Merchant API
Checking…Sandbox

Idempotency

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

JavaScript
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.