Best Practices
A few things that'll help you build a solid, reliable integration.
Idempotency
Networks aren't perfect. Requests can time out, connections can drop, and retries can happen. The Idempotency-Key header makes sure that if you accidentally send the same request twice, you don't end up with duplicate payments.
How it works
Include a unique Idempotency-Key with any POST request:
curl -X POST https://api.yeti.host/v1/ecom/YOUR_SITE_ID/payments \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: order-12345-payment-attempt-1" \
-H "Content-Type: application/json" \
-d '{ ... }'
If we see the same key again, we'll give you back the result from the first time instead of processing a new one.
A few tips
- Make the key something meaningful from your side, like
{orderId}-{attemptNumber}. That way, retries naturally use the same key. - Always include one for the important stuff: payments, sessions, captures, and reversals.
- There's no need to overthink it -- just pick something that's unique per logical operation and consistent across retries.
Keeping things secure
Your API key is a secret
This one's important: your API key belongs on your server, never in the browser. The only thing that's safe to use on the frontend is the clientToken you get from GET /config.
Store keys properly
Use environment variables or a secrets manager. Don't put them in source control, config files that get committed, or anywhere they might accidentally end up somewhere public.
Rotate regularly
Create a new key, update your integration to use it, then turn the old one off. That way even if a key does leak, the window of exposure is small.
Only ask for what you need
Keep your API keys scoped to the minimum access your integration actually uses, and avoid exposing functionality you don't need. Keep things tight.
Everything goes over HTTPS
All communication with the API uses HTTPS. We don't accept plain HTTP. The same goes for webhook target URLs -- they need to be HTTPS too.
Handling 3D Secure
3D Secure adds an extra verification step for card payments. Most card payments will go through it.
If you're using Sessions
You don't need to do anything. The Adyen Drop-in handles 3DS automatically.
If you're using the Advanced flow
- After calling
POST /payments, check if the response has anactionobject. - If it does, pass it to the Adyen Component:
if (result.action) {
component.handleAction(result.action);
} - The Component shows the 3DS challenge to the customer.
- When they're done, the Component fires
onAdditionalDetailswith the result. - Send those details to
POST /payments/detailsto get the final outcome.
Redirects
Some payment methods and 3DS flows send the customer to an external page. Make sure your returnUrl is right and that your app handles the return properly -- the customer will come back to that URL when they're finished.
Use reference fields
The reference parameter on payments, captures, and reversals lets you attach your own identifiers. This makes life much easier when it comes to:
- Matching things up: Connect API operations to your internal orders.
- Debugging: Find transactions quickly when something needs investigating.
- Reporting: Search and filter by your own references.
References can be up to 80 characters.
Rate limiting
We have rate limits in place to keep things fair for everyone. If you hit the limit, you'll get a 429 Too Many Requests back with error code yp_9006.
What to do about it
The best approach is exponential backoff with a bit of randomness:
async function requestWithRetry(fn, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const response = await fn();
if (response.status !== 429) {
return response;
}
const delay = Math.min(1000 * Math.pow(2, attempt), 30000);
const jitter = Math.random() * 1000;
await new Promise((resolve) => setTimeout(resolve, delay + jitter));
}
throw new Error("Max retries exceeded");
}
If you're consistently hitting limits, get in touch and we can talk about your usage patterns.
Webhooks
- Reply with 200 quickly. If you need to do something slow, put the event on a queue and respond straight away. If you take too long, we'll time out and try again.
- Handle duplicates. Retries mean you might get the same event more than once. Use the
X-Webhook-IdorpspReferenceto spot them. - Webhooks are the source of truth. The response from a payment call gives you the initial result, but the webhook tells you what actually happened in the end. Always reconcile against webhooks.
- Always verify the signature. Before you do anything with a webhook, make sure it's really from us. See Checking that a webhook is genuine.
Error handling
- Use the
errorCodefield for programmatic handling -- don't rely on themessage, which might change. - Retryable errors (500, 429): Try again with the same idempotency key.
- Non-retryable errors (400, 401, 403): Something's wrong with the request itself. Fix it before trying again.
- Log the full error response when something goes wrong -- the
errorCodeandmessagetogether will usually tell you what's up.
For the full rundown, see Error Handling.