Billium

Idempotency

Safely retry invoice creation without producing duplicates.

Network retries are inevitable. When your own code retries a POST /invoices — because a request timed out, a deploy rotated a pod mid-flight, or a load balancer dropped a connection — you need a guarantee that the second attempt will not create a second invoice.

Billium provides that guarantee through the Idempotency-Key HTTP header.

How it works

  1. You generate a unique key per logical operation (one per checkout session, one per cart submission — not one per retry).
  2. You send it on every POST attempt in the Idempotency-Key header.
  3. If Billium has already seen that key in the last 24 hours, it returns the exact response from the first attempt and does not run the creation logic again.
  4. If Billium has seen that key but the request body is different, it returns 409 Conflict — that's a programming bug, not a transient error.

The key is scoped to your merchant. You do not need to worry about collisions with other Billium customers.

Generating a key

Any string ≤ 255 characters works. A UUID v4 is a solid default.

import { randomUUID } from 'crypto';
import { Billium } from '@billium/node';

const billium = new Billium({
  apiKey: process.env.BILLIUM_API_KEY,
  merchantId: process.env.BILLIUM_MERCHANT_ID,
});

const invoice = await billium.invoices.create(
  { name: 'Order #8821', rawAmount: 49.99 },
  { idempotencyKey: randomUUID() },
);
import os, uuid, httpx

httpx.post(
    f"https://api.billium.to/api/v1/merchants/merchant/{os.environ['BILLIUM_MERCHANT_ID']}/invoices",
    headers={
        "x-api-key": os.environ["BILLIUM_API_KEY"],
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={"name": "Order #8821", "rawAmount": 49.99},
)
curl -X POST \
  https://api.billium.to/api/v1/merchants/merchant/$BILLIUM_MERCHANT_ID/invoices \
  -H "x-api-key: sk_..." \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"name": "Order #8821", "rawAmount": 49.99}'

Best practice: derive the key from your business event

UUID v4 is fine, but deriving the key from a stable identifier of the business event — e.g. checkout-{sessionId} — gives you idempotency across server restarts too. If the request succeeds but your process dies before persisting the invoice ID, replaying with the same derived key safely returns the original invoice instead of creating a duplicate.

const invoice = await billium.invoices.create(
  { name: `Order #${cart.id}`, rawAmount: cart.totalUsd },
  { idempotencyKey: `checkout-${cart.id}` },
);

Idempotency and SDK retries

The Node SDK will not retry a POST unless you set Idempotency-Key. This is intentional: without that header, the SDK cannot prove the server did not already accept the first attempt, and retrying could silently double-charge your customer.

With the key set:

  • Network errors during create() are retried.
  • 429, 500, 502, 503, 504 responses are retried with exponential backoff and full jitter.
  • Retry-After headers are honored.

Conflicts

If you reuse the same key with a different body within the 24-hour window, Billium returns:

{
  "statusCode": 409,
  "message": "Idempotency-Key conflict: this key was previously used with a different request body",
  "error": "Conflict"
}

The Node SDK raises this as BilliumApiError with status === 409. Treat it as a bug in your caller — either the key was reused across unrelated operations, or the request body varied between attempts (e.g. because a timestamp or currency conversion was computed twice).

On this page