DevelopersBuild safely
Idempotency
Every v1 write needs an Idempotency-Key. How keys are kept for 24 hours, what a replay returns, and the five idempotency errors.
Every v1 write needs an Idempotency-Key header. Today that is the five POST endpoints: create a
checkout session, cancel it, reissue its link, cancel a subscription and send a test event. If a
request fails on the network, you send it again with the same key, and Fianto answers with the
first response instead of doing the work twice.
This matters most for creating a checkout session: its url comes back only in the create response.
A retry under the same key returns that stored response, url included.
The key
- 1 to 255 printable ASCII characters, with no spaces. Keys are separate per application.
- Kept for 24 hours. Within that time, the same key with the same request (method, path, body and query) returns the stored
response, with the header
Idempotent-Replayed: true. - If the first request failed with an error, the key is released, and you can use it again.
curl https://api.fianto.xyz/v1/checkout-sessions/SESSION_ID/cancel \
--user "$FIANTO_APP_ID:$FIANTO_APP_SECRET" \
-H "Idempotency-Key: cancel-SESSION_ID-1" \
-X POSTIn the SDK
The SDK sends a key with every POST on its own: a crypto.randomUUID() made once per call and
reused on every automatic retry of that call. That covers a lost response while your process keeps
running.
If your process can crash between creating a session and saving its url, derive the key from data
you already have, so a restarted process sends the same key:
import { Fianto } from '@fianto/sdk';
const fianto = new Fianto();
export async function createCheckout(orderId: string, attempt: number) {
return fianto.checkoutSessions.create(
{
mode: 'payment',
order_id: orderId,
amount: '12.50',
description: 'Coffee beans, 1 kg',
success_url: 'https://shop.example/thank-you',
cancel_url: 'https://shop.example/cart',
},
// The same order and attempt always send the same key.
{ idempotencyKey: `checkout:${orderId}:${attempt}` },
);
}When the SDK gives up on a network failure, the ConnectionError or TimeoutError carries the
idempotencyKey it used. Retry the same call with that key to recover:
import { ConnectionError, Fianto, TimeoutError } from '@fianto/sdk';
import type { CheckoutSessionCreateParams } from '@fianto/sdk';
const fianto = new Fianto();
export async function createWithRecovery(params: CheckoutSessionCreateParams) {
try {
return await fianto.checkoutSessions.create(params);
} catch (err) {
if ((err instanceof ConnectionError || err instanceof TimeoutError) && err.idempotencyKey) {
return fianto.checkoutSessions.create(params, { idempotencyKey: err.idempotencyKey });
}
throw err;
}
}A new key is a new request
Retrying with a fresh key runs the request again. For a create whose first attempt did succeed, the
second call finds the order's open session and returns it with url: null, so you lose the link
until you reissue it. Reuse the key whenever you repeat a write.
Troubleshooting
| You see | Why | Fix |
|---|---|---|
400 idempotency_key_required | A POST was sent without an Idempotency-Key header, or with an empty one. | Add the header. The SDK does this for you. |
400 idempotency_key_invalid | The key is longer than 255 characters, or has spaces or non-printable characters. | Use 1 to 255 printable ASCII characters with no spaces. |
422 idempotency_key_reused | The key was already used with a different request. | Use a new key for a new request. The SDK does not retry this error. |
409 idempotency_request_in_progress | The first request with this key is still running. | Retry with the same key in a few seconds. The SDK retries this one. |
409 idempotency_response_unreadable | The request already completed, but its saved response can no longer be read. | Fetch the resource to see the result, for example GET /v1/orders/lookup?order_id=. Do not resend under a new key without checking first. |
See also
Was this page helpful? Tell us