# Idempotency Idempotency enables safe re-submission of requests, eliminating the risk of unintentionally duplicating operations. ## What is Idempotency? 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 ## How It Works To make an idempotent request, include an `Idempotency-Key` header with a unique identifier: ```bash 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" }' ``` ### What Happens 1. **First Request**: Easypay processes the request and stores the response with the idempotency key 2. **Duplicate Requests**: Subsequent requests with the same key return the stored response without re-processing 3. **Different Payload**: If the request body differs, an error is returned to prevent accidental misuse ## Detecting Replayed Requests When a request is a replay, the response includes an `Idempotency-Replay` header: ``` Idempotency-Replay: true ``` This helps you distinguish between original and replayed requests in your application logic. ## Generating Idempotency Keys **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 // JavaScript (using UUID v4) const { v4: uuidv4 } = require('uuid'); const idempotencyKey = uuidv4(); ``` ```python # Python (using UUID v4) import uuid idempotency_key = str(uuid.uuid4()) ``` ```php // PHP (using ramsey/uuid) use Ramsey\Uuid\Uuid; $idempotencyKey = Uuid::uuid4()->toString(); ``` ## Key Behavior ### Account-Specific Keys 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 independently ``` ### Request Body Comparison The 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 ```json { "status": "error", "message": "Idempotency Error: Request body differs from original request", "code": "IDEMPOTENCY_MISMATCH" } ``` ### Transient Errors Idempotency keys are **not** stored for transient errors, allowing you to retry safely: - `429 Too Many Requests` - `502 Bad Gateway` - `503 Service Unavailable` For these errors, you can retry with the same idempotency key. ### Key Expiration Idempotency keys are automatically purged from the system **24 hours** after creation. After expiration, the same key can be used for a new request. ## When to Use Idempotency ### Supported Operations Idempotency keys are supported on: - **POST** requests - **PATCH** requests ### Not Needed For - **GET** requests (inherently idempotent) - **DELETE** requests (inherently idempotent) ### Use Cases **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 ## Retry Strategy with Idempotency When using idempotency keys, follow this retry strategy: ```javascript 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; } } } ``` ## Best Practices 1. **Always Use for Payments**: Include idempotency keys on all payment-related requests 2. **Generate Fresh Keys**: Create a new idempotency key for each unique operation 3. **Store Keys**: Save idempotency keys in your database to track which operations completed 4. **Check Replay Header**: Use the `Idempotency-Replay` header to detect replayed requests 5. **Respect Expiration**: Don't reuse keys after 24 hours 6. **Handle Mismatches**: If you get an idempotency mismatch error, generate a new key ## Example: Creating a Payment Safely ```javascript 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; } } ``` ## Next Steps - [Error Handling](/docs/error-handling) - Learn about error responses and retry strategies - [Quick Start](/docs/quickstart) - Make your first idempotent API call - [API Reference](/openapi) - See which endpoints support idempotency