All use cases
API design11 min readBackend engineers building payment flows

Designing idempotent payment APIs for AI agents

AI agents retry aggressively. If your payment endpoint is not idempotent, one timeout can become two payments. This tutorial shows how to design around that failure mode.

The real failure mode

A bot sends a payment request. The API succeeds, but the network times out before the bot receives the response. The bot retries. Without idempotency, the second request can move money again.

This is common in scheduled jobs, Telegram bots, marketplace payouts, and agent workflows because the caller often cannot tell whether the first request reached the server. Idempotency makes retries safe by tying a logical payment to a stable key.

Where the idempotency key comes from

  • Use a business identifier, not a random value.
  • Good keys include invoice IDs, job IDs, signal IDs, payout IDs, or access purchase IDs.
  • Bad keys include Date.now(), random UUIDs generated on every retry, or UI session IDs.
  • Keep the same key when retrying the same logical action.

Payment request model

type PaymentCommand = {
  fromWalletId: string;
  toHandle?: string;
  toWalletId?: string;
  amount: number;
  token: "USDC" | "USDT" | "PYUSD" | "EURC";
  memo: string;
  idempotencyKey: string;
};

Call Viaclave with a stable key

async function payInvoice(invoiceId: string, vendorHandle: string) {
  const response = await fetch("https://api.viaclave.com/v1/payments/send", {
    method: "POST",
    headers: {
      "Authorization": "Bearer vc_live_YOUR_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      from_wallet_id: "wal_platform_treasury",
      to_handle: vendorHandle,
      amount: 2500000,
      token: "USDC",
      memo: "Invoice " + invoiceId,
      idempotency_key: "invoice-" + invoiceId,
    }),
  });

  return response.json();
}

Store local attempts

Your app should also keep its own command table. This lets you distinguish a payment that was never sent from one that was sent but did not return a response.

type PaymentAttempt = {
  idempotencyKey: string;
  localStatus: "created" | "submitted" | "confirmed" | "failed";
  paymentId: string | null;
  lastError: string | null;
  createdAt: string;
  updatedAt: string;
};

Retry policy

  • Retry network errors with the same idempotency key.
  • Do not retry validation errors until the command payload changes.
  • Show operators the idempotency key in support tooling.
  • Use exponential backoff for automated jobs.
  • Reconcile uncertain attempts by checking payment status before sending a new command.

ROI of getting this right

Duplicate payments are expensive because they create financial loss and support work at the same time. Idempotency is cheap compared with refund handling, angry providers, and manual ledger repair.

If an agent platform sends thousands of small payments, even a tiny duplicate rate creates noise. Idempotency turns retries from a financial risk into normal infrastructure behavior.

Build this workflow in test mode

Create a test API key, connect the MCP server, or call the REST API directly. Viaclave's test mode lets you try wallet creation and test stablecoin payments without real funds.