Guide
Markdown versionIdempotency
How to safely retry a purchase after a timeout or a dropped connection.
If the response to POST /purchase never arrived, you don't know whether the purchase went through. An unprotected retry is a second purchase and a second charge. So send an idempotencyKey — your own unique purchase identifier, for example the order ID in your system.
- The first request with a key creates the order.
- A repeat with the same key and the same product returns the outcome of that same order — with the same status code and
"replayed": true. Nothing is charged twice, even if the product has sold out since. - The same key with a different product —
422 idempotency_key_reused. - If no order was created (e.g.
402, insufficient balance), the key stays free: after a top-up you can retry with it.
Format
1–128 printable ASCII characters (!…~), no spaces or non-Latin letters. Case matters: ABC and abc are two different purchases. A key is scoped to your client.
Retrying with the same key
async function buy(productId, idempotencyKey) {
for (let attempt = 1; attempt <= 5; attempt++) {
try {
const res = await fetch(`https://merchant.dbdlobby.com/purchase/${productId}`, {
method: "POST",
headers: { "X-Entity-Key": KEY, "Content-Type": "application/json" },
body: JSON.stringify({ idempotencyKey }), // the same key in every attempt
signal: AbortSignal.timeout(90_000),
});
if (res.status >= 500 && res.status !== 502) throw new Error(`HTTP ${res.status}`);
return { status: res.status, body: await res.json() };
} catch (e) {
// Network, timeout or 5xx: the outcome is unknown — retry WITH THE SAME key.
await new Promise((r) => setTimeout(r, 1000 * 2 ** attempt));
}
}
throw new Error("API unavailable; retry later with the same idempotencyKey");
}The key is optional, but without it a retry after a timeout creates a second purchase. It may become mandatory in the future.