Idempotency & retries
Every write takes an idempotency key. It is what makes a retry safe: the second call returns the first call's result instead of charging you again.
Why it is mandatory
A request that times out tells you nothing about whether it arrived. Without a key, retrying risks issuing a second subscription and paying for it; not retrying risks leaving a paid customer with nothing. The key removes the guess — retry freely, and you get the original outcome back.
Choosing a key
Any string of 8 to 128 characters. A UUID per logical operation is the simplest choice that works. Keys are scoped to your account, so you never need to worry about colliding with another partner.
What a replay returns
A repeated call with the same key and the same body returns the original response verbatim — the same order id, the same remaining balance, and for a promo batch the same codes. No additional quota is spent.
Two responses mean the retry needs handling rather than acceptance:
The first call is still running. Wait and retry with the same key — do not start a new one.
The key was used before with a different body. This is a bug on your side — usually a key reused across two different sales. Nothing was charged.
A retry loop that behaves
// Mint the key once, before the first attempt, and persist it
// alongside your own record of the sale.
const key = order.idempotencyKey ?? crypto.randomUUID();
await db.orders.update(order.id, { idempotencyKey: key });
for (let attempt = 0; attempt < 3; attempt++) {
const res = await fetch(url, {
method: "POST",
headers,
body: JSON.stringify({ planSlug, login, idempotencyKey: key }),
});
if (res.ok) return res.json();
// 409 RequestInProgress means an identical call is still running.
const problem = await res.json().catch(() => null);
const code = problem?.type?.replace("/errors/", "");
if (code === "Reseller.RequestInProgress" || res.status >= 500) {
await sleep(1000 * 2 ** attempt);
continue;
}
// Anything else is final — retrying will not change the outcome.
throw new Error(code ?? `HTTP ${res.status}`);
}The rule of thumb: retry on network failures, on RequestInProgress, and on 5xx. Treat every other 4xx as final, because the same
request will keep failing — a misspelled login or an unknown plan
slug needs fixing, not repeating.
