Search

Are you looking for test card numbers?

Would you like to contact support?

Developer-resource icon

API idempotency

Learn how to safely resend API requests without performing the same operation multiple times.

The Adyen API supports idempotency, allowing you to retry a request multiple times while only performing the action once. This helps avoid unwanted duplication in case of failures and retries. For example, in the case of a timeout error, it is possible to safely retry sending the same API payment call multiple times with the guarantee that the payment detail will only be charged once.

The accounting rules in the Adyen payments platform take care of most potential double-processing issues that can impact payment modifications. For example, the following default rules apply:

  • Captures: While partial captures are allowed, it is not possible to capture a higher amount than the authorized one.
  • Refunds: While multiple refunds are allowed, by default the total refunded value cannot exceed the captured amount.

To minimize unwanted side effects when requests are duplicated, you can also take the following actions on your end:

  • Implement asynchronous server-to-server notification webhooks. For example, this approach helps keep track of missing responses, a common consequence of a data transmission timeout.
  • Enable idempotency in your API requests. The Adyen API supports idempotency on POST requests (other request types such as GET, DELETE and PUT are idempotent by definition). 

Enable idempotency

To submit a request for idempotent processing, send a request with the Idempotency-Key:<key> in the header. The <key> is a unique identifier for the message with a maximum of 64 characters (we recommend a UUID). If you don't receive a response (for example, in case of a timeout), you can safely retry the request with the same header. If the Adyen payments platform already processed the request, the response to the first attempt will be returned without duplication.

To verify that a request was processed idempotently, check the Idempotency-Key HTTP header returned in the response.

Here is an example of how you include the Idempotency-Key in a /payments request:

curl https://checkout-test.adyen.com/v64/payments \
-H "x-API-key: YOUR_X-API-KEY" \
-H "content-type: application/json"
-H "Idempotency-Key: UNIQUE-ID-dad126b9-d7b3-4d8d-adf0-7c6e324"\
-d '{
  "amount":{
    "currency":"EUR",
    "value":1000
  },
  "reference":"YOUR_ORDER_NUMBER",
  "paymentMethod":
    "type": "scheme",
    "encryptedCardNumber": "test_4111111111111111",
    "encryptedExpiryMonth": "test_03",
    "encryptedExpiryYear": "test_2030",
    "encryptedSecurityCode": "test_737"
  },
  "returnUrl":"https://your-company.com/checkout?shopperOrder=12xy..",
  "merchantAccount":"YOUR_MERCHANT_ACCOUNT"
}'
require 'adyen-ruby-api-library'

# Set your X-API-KEY with the API key from the Customer Area.
adyen = Adyen::Client.new
adyen.env = :test
adyen.api_key = "YOUR_X-API-KEY"

request = {
  "amount": {
    "currency": "EUR",
    "value": 1000
  },
  "reference": "YOUR_ORDER_NUMBER",
  "paymentMethod": {
    "type": "scheme",
    "encryptedCardNumber": "test_4111111111111111",
    "encryptedExpiryMonth": "test_03",
    "encryptedExpiryYear": "test_2030",
    "encryptedSecurityCode": "test_737"
  },
  "returnUrl": "https://your-company.com/checkout?shopperOrder=12xy..",
  "merchantAccount": "YOUR_MERCHANT_ACCOUNT"
}
headers = {"Idempotency-Key": "UNIQUE-ID-dad126b9-d7b3-4d8d-adf0-7c6e324"}
response = adyen.checkout.payments(request, headers)
// Set your X-API-KEY with the API key from the Customer Area.
Client client = new Client(xApiKey,Environment.TEST);

RequestOptions requestOptions = new RequestOptions();
requestOptions.setIdempotencyKey("UNIQUE-ID-dad126b9-d7b3-4d8d-adf0-7c6e324");

Checkout checkout = new Checkout(client);
PaymentsRequest paymentsRequest = new PaymentsRequest();
paymentsRequest.setMerchantAccount("YOUR_MERCHANT_ACCOUNT");
Amount amount = new Amount();
amount.setCurrency("EUR");
amount.setValue(1000L);
paymentsRequest.setAmount(amount);
String encryptedCardNumber = "test_4111111111111111";
String encryptedExpiryMonth = "test_03";
String encryptedExpiryYear = "test_2030";
String encryptedSecurityCode = "test_737";
paymentsRequest.setReference("YOUR_ORDER_NUMBER");
paymentsRequest.addEncryptedCardData(encryptedCardNumber,encryptedExpiryMonth, encryptedExpiryYear, encryptedSecurityCode);
paymentsRequest.setReturnUrl("https://your-company.com/checkout?shopperOrder=12xy..");

PaymentsResponse paymentsResponse = new Checkout(client).payments(paymentsRequest, requestOptions);
// Set your X-API-KEY with the API key from the Customer Area.
$client = new \Adyen\Client();
$client->setXApiKey("YOUR_X-API-KEY");
$service = new \Adyen\Service\Checkout($client);
$requestOptions['idempotencyKey'] = "UNIQUE-ID-dad126b9-d7b3-4d8d-adf0-7c6e324";
$params = array(
  "amount" => array(
    "currency" => "EUR",
    "value" => 1000
  ),
  "reference" => "YOUR_ORDER_NUMBER",
  "paymentMethod" => array(
    "type" => "scheme",
    "encryptedCardNumber" => "test_4111111111111111",
    "encryptedExpiryMonth" => "test_03",
    "encryptedExpiryYear" => "test_2030",
    "encryptedSecurityCode" => "test_737"
  ),
  "returnUrl" => "https://your-company.com/checkout?shopperOrder=12xy..",
  "merchantAccount" => "YOUR_MERCHANT_ACCOUNT"
);
$result = $service->payments($params, $requestOptions);
// Set your X-API-KEY with the API key from the Customer Area.
var client = new Client ("YOUR_X-API-KEY", Environment.Test);
var checkout = new Checkout(client);

var amount = new Model.Checkout.Amount("EUR", 1000);
var details = new Model.Checkout.DefaultPaymentMethodDetails{
  Type = "scheme",
  EncryptedCardNumber = "test_4111111111111111",
  EncryptedExpiryMonth = "test_03",
  EncryptedExpiryYear = "test_2030",
  EncryptedSecurityCode = "test_737"
};
var paymentRequest = new Adyen.Model.Checkout.PaymentRequest
{
  Reference = "YOUR_ORDER_NUMBER",
  Amount = amount,
  ReturnUrl = @"https://your-company.com/checkout?shopperOrder=12xy..",
  MerchantAccount = "YOUR_MERCHANT_ACCOUNT",
  PaymentMethod = details
};

var paymentResponse = checkout.Payments(paymentsRequest,new RequestOptions { IdempotencyKey= "IdempotencyKey" });
var {Config, Client, CheckoutAPI} = require('@adyen/api-library')
// Set your X-API-KEY with the API key from the Customer Area.
var config = new Config({ apiKey: '[YOUR_API_KEY]', environment: 'TEST', merchantAccount: 'YOUR_MERCHANT_ACCOUNT' })
var client = new Client(config)
var checkout = new CheckoutAPI(client)
checkout.payments(
  {
    amount: { currency: "EUR", value: 1000 },
    paymentMethod: {
        type: 'scheme',
        encryptedSecurityCode: "test_4111111111111111",
        encryptedExpiryMonth: "test_03",
        encryptedExpiryYear: "test_2030",
        encryptedCardNumber: "test_737"
    },
    reference: "YOUR_ORDER_NUMBER",
    merchantAccount: config.merchantAccount,
    returnUrl: "https://your-company.com/checkout?shopperOrder=12xy.."
  },
  { idempotencyKey: "UNIQUE-ID-dad126b9-d7b3-4d8d-adf0-7c6e324" } // RequestOptions
)

Key scope and validity time

Keys are stored at a company account level. The system checks that the idempotency keys are unique to the company account. Idempotency keys are valid for a minimum period of 31 days after first submission (but may be retained for longer).

If you are targeting multiple regional endpoints simultaneously, idempotency keys will not be checked for duplication in other regions.

Security considerations

Generate idempotency keys using the version 4 (random) UUID type to prevent two API credentials under the same account from accessing each others responses.

Lowering the access level for an API credential does not prevent them from retrieving past responses if the user still has access to previously used keys.

Error handling

Transient errors

In rare instances, you could receive a transient error. This is a temporary error that has no side effects and can be retried. For example, it could come from a race condition of sending two payment requests with the same idempotency key at the same time. One will end up being processed while the other will return a transient error.

The response will contain an HTTP header Transient-Error with the value true, which indicates that the request can be retried at a later time using the same idempotency key. If the API does not return a transient error header, or returns a header with a value of false, do not retry the request. If you submit a duplicate request before the first request has completed, the API returns an HTTP 409 – Conflict status with the error code 704: "request already processed or in progress".

Retries and exponential backoff

Use an exponential backoff strategy when retrying transactions to avoid flooding the API and running into rate-limiting. See the Wikipedia article on exponential backoff for more information.

Designing for resilience

To enable idempotent behavior, Adyen must retain a consistent, stateful data store to compare each request against. Idempotent processing relies on this data store being available. In the unlikely event that this data store should become unavailable, the API returns an HTTP 503 – Service Unavailable status with the error code 703: "required resource temporarily unavailable". The system marks the request as a transient error (see above).

If there are many of these responses, you can either:

  • Pause processing and retry the requests later (if this is feasible).
  • Fall back to non-idempotent processing by not submitting the Idempotency-Key header.