Idempotency enables safe re-submission of requests, eliminating the risk of unintentionally duplicating operations.
Idempotency ensures that making the same API call multiple times has the same effect as making it once. This is crucial when:
- Network timeouts occur
- You're unsure if a request was processed
- You need to retry failed requests safely
To make an idempotent request, include an Idempotency-Key header with a unique identifier:
curl -L -X POST 'https://api.test.easypay.pt/2.0/single' \
-H 'AccountId: 2b0f63e2-9fb5-4e52-aca0-b4bf0339bbe6' \
-H 'ApiKey: eae4aa59-8e5b-4ec2-887d-b02768481a92' \
-H 'Idempotency-Key: 435e08a0-e5a9-4216-acb5-44d6b96de612' \
-H 'Content-Type: application/json' \
--data-raw '{
"type": "sale",
"value": 10.00,
"currency": "EUR",
"method": "cc"
}'- First Request: Easypay processes the request and stores the response with the idempotency key
- Duplicate Requests: Subsequent requests with the same key return the stored response without re-processing
- Different Payload: If the request body differs, an error is returned to prevent accidental misuse
When a request is a replay, the response includes an Idempotency-Replay header:
Idempotency-Replay: trueThis helps you distinguish between original and replayed requests in your application logic.
Best Practices:
- Use UUIDs (v4) or ULIDs for idempotency keys
- Generate a new key for each unique operation
- Maximum key length: 50 characters
- Keys should have sufficient entropy to prevent collisions
Examples:
// JavaScript (using UUID v4)
const { v4: uuidv4 } = require('uuid');
const idempotencyKey = uuidv4();# Python (using UUID v4)
import uuid
idempotency_key = str(uuid.uuid4())// PHP (using ramsey/uuid)
use Ramsey\Uuid\Uuid;
$idempotencyKey = Uuid::uuid4()->toString();Two requests with the same Idempotency-Key but different AccountId headers are treated as different requests.
Request 1: AccountId: account-1, Idempotency-Key: key-123
Request 2: AccountId: account-2, Idempotency-Key: key-123
Result: Both requests are processed independentlyThe idempotency layer compares the request body of incoming requests with the original:
- Same body: Returns the cached response
- Different body: Returns an error to prevent accidental misuse
{
"status": "error",
"message": "Idempotency Error: Request body differs from original request",
"code": "IDEMPOTENCY_MISMATCH"
}Idempotency keys are not stored for transient errors, allowing you to retry safely:
429 Too Many Requests502 Bad Gateway503 Service Unavailable
For these errors, you can retry with the same idempotency key.
Idempotency keys are automatically purged from the system 24 hours after creation. After expiration, the same key can be used for a new request.
Idempotency keys are supported on:
- POST requests
- PATCH requests
- GET requests (inherently idempotent)
- DELETE requests (inherently idempotent)
Essential:
- Payment creation
- Refund processing
- Any operation that modifies financial data
Recommended:
- Any POST or PATCH request
- Operations that create or update resources
- Requests that may be retried
When using idempotency keys, follow this retry strategy:
async function safeRequest(url, payload, maxRetries = 3) {
const idempotencyKey = generateUUID();
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'AccountId': process.env.EASYPAY_ACCOUNT_ID,
'ApiKey': process.env.EASYPAY_API_KEY,
'Idempotency-Key': idempotencyKey,
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
const shouldRetry = response.headers.get('X-Easypay-Should-Retry');
if (response.ok) {
return await response.json();
}
if (shouldRetry === 'true' && i < maxRetries - 1) {
await sleep(Math.pow(2, i) * 1000); // Exponential backoff
continue;
}
throw new Error(`Request failed: ${response.status}`);
} catch (error) {
if (i < maxRetries - 1) {
await sleep(Math.pow(2, i) * 1000);
continue;
}
throw error;
}
}
}- Always Use for Payments: Include idempotency keys on all payment-related requests
- Generate Fresh Keys: Create a new idempotency key for each unique operation
- Store Keys: Save idempotency keys in your database to track which operations completed
- Check Replay Header: Use the
Idempotency-Replayheader to detect replayed requests - Respect Expiration: Don't reuse keys after 24 hours
- Handle Mismatches: If you get an idempotency mismatch error, generate a new key
const axios = require('axios');
const { v4: uuidv4 } = require('uuid');
async function createPayment(paymentData) {
const idempotencyKey = uuidv4();
try {
const response = await axios.post(
'https://api.prod.easypay.pt/2.0/single',
paymentData,
{
headers: {
'AccountId': process.env.EASYPAY_ACCOUNT_ID,
'ApiKey': process.env.EASYPAY_API_KEY,
'Idempotency-Key': idempotencyKey,
'Content-Type': 'application/json'
}
}
);
// Check if this was a replayed request
const wasReplayed = response.headers['idempotency-replay'] === 'true';
if (wasReplayed) {
console.log('This payment was already processed');
}
return response.data;
} catch (error) {
// Check if we should retry
const shouldRetry = error.response?.headers['x-easypay-should-retry'];
if (shouldRetry === 'true') {
// Retry with the same idempotency key
// ... implement retry logic
}
throw error;
}
}- Error Handling - Learn about error responses and retry strategies
- Quick Start - Make your first idempotent API call
- API Reference - See which endpoints support idempotency