Idempotency
Overview
The Yugo Payments API uses idempotency keys to allow safe retries of requests without accidentally performing the same operation multiple times. This is especially important for payment operations where duplicate charges or payouts could have financial consequences.
How It Works
When making a POST request to create a resource (Payin, Payout, or Refund), you must include an Idempotency-Key header with a unique identifier for that specific request.
POST /api/v2/payins
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
X-API-Key: your_api_key
Content-Type: application/json
{
"amount": {
"value": "100.00",
"currency": "EUR"
},
...
}
Key Behaviors
-
First Request: When you send a request with a new idempotency key, the API processes it normally and creates the resource.
-
Retry with Same Key: If you retry the request with the same idempotency key (e.g., due to a network timeout), the API will:
- Not create a new resource
- Return the previously created resource with the same response code (e.g.,
201 Created) - Process the retry much faster since no new operation is performed
-
Different Request, Same Key: If you send a different request body with the same idempotency key, the API will still return the original resource. The idempotency key binds to the first request that used it.
Choosing an Idempotency Key
The idempotency key should be:
- Unique per logical operation: Each distinct payment intent should have its own key
- Deterministic: Use the same key when retrying the same operation
- Max 36 characters: UUID format is recommended
- Associated with your system's identifier: Many merchants use their order ID, invoice number, or similar
Examples
// Good: Using a UUID
const idempotencyKey = "550e8400-e29b-41d4-a716-446655440000";
// Good: Using your order ID
const idempotencyKey = `order_${orderId}_payin`;
// Bad: Random value on each retry (defeats the purpose)
const idempotencyKey = Math.random().toString();
Best Practices
1. Store Keys with Your Records
Save the idempotency key alongside your order/transaction record in your database:
const order = {
id: "order_123",
idempotencyKey: "550e8400-e29b-41d4-a716-446655440000",
payinId: null, // Will be filled after successful creation
};
2. Implement Retry Logic
Use exponential backoff when retrying failed requests:
async function createPayinWithRetry(payinIntent, idempotencyKey, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch('https://api.yugo.finance/api/v2/payins', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': apiKey,
'Idempotency-Key': idempotencyKey,
},
body: JSON.stringify(payinIntent),
});
if (response.ok) {
return await response.json();
}
if (response.status >= 400 && response.status < 500) {
// Client error - don't retry
throw new Error(`Client error: ${response.status}`);
}
// Server error or timeout - retry with backoff
await sleep(Math.pow(2, attempt) * 1000);
} catch (error) {
if (attempt === maxRetries - 1) throw error;
await sleep(Math.pow(2, attempt) * 1000);
}
}
}
3. Handle Network Timeouts
Even if your request times out, it may have been processed by the server:
try {
const payin = await createPayin(intent, idempotencyKey);
} catch (error) {
if (error.type === 'timeout') {
// Retry with the SAME idempotency key
const payin = await createPayin(intent, idempotencyKey);
}
}
4. Key Expiration
Idempotency keys are stored for 24 hours after the first use. After this period:
- The key can be reused for a new operation
- We recommend using fresh keys for new operations to avoid confusion
Error Scenarios
Scenario 1: Request Succeeds, Response Lost
No duplicate charge, same Payin returned.
Scenario 2: Server Error Before Processing
Retry succeeds, Payin created on second attempt.
Scenario 3: Different Request, Same Key
Original Payin returned, new parameters ignored.
Common Questions
Q: What happens if I don't provide an idempotency key?
A: The Idempotency-Key header is required for all POST operations that create resources. Requests without this header will be rejected with a 400 Bad Request error.
Q: How do I know if a response is from the original request or a retry?
A: The response is identical in both cases. Check your database to see if you've already recorded the resource ID.
Q: What if I need to change the amount after the first request failed?
A: Amount is immutable and could not be changed. Instead, create a new request with a new idempotency key. The first one will expire eventually.