Skip to main content
HomeLanding pagesHow to handle failed agent payments
LANDING PAGE

How to handle failed agent payments

8 min read·Last updated June 2, 2026

A paid call can fail because the spend limit refused it, the wallet is empty, settlement errored (a 503 while a chain adapter is in rollout), or the upstream itself failed. Each needs a different response, and none should be a blind retry of the same call. Classify the failure from the status and any reason, then stop, top up, back off, or escalate accordingly.

Why failures need handling

A paid call will fail sometimes, and how the agent reacts decides whether that is a small handled event or a runaway. The wrong reflex is a blind retry: the same call, again, immediately. For most payment failures that does nothing useful and some it makes worse, turning one blocked call into a tight loop that wastes turns and looks exactly like an attack in your logs. The right approach is to classify the failure and respond to its actual cause.

This page is about that handling logic. Preventing overspend in the first place is how-to-prevent-ai-agent-from-overspending, and testing these paths is how-to-test-agent-payments. The payment API product page is the broader reference.

The kinds of failure

There are four causes, and they want different responses, so it is worth holding them distinct.

A payment refused by the spend limit: the call exceeds the per-transaction cap or the period allowance, and the wallet refuses before settlement. An insufficient balance: the wallet is empty and cannot pay. A settlement error: the settle path errored, including a 503 on a network whose chain adapter is still being rolled out, which is transient. And an upstream failure: the payment was fine but the paid service itself returned an error. Lumping these together as "payment failed" and retrying is the mistake; they are different problems with different fixes.

Prerequisites

Classify before you react

Get the failure's cause before deciding what to do. On the pay side, a non-2xx from the call tells you it failed, and the status plus any reason tell you why. Branch on the cause rather than treating every failure the same.

TYPESCRIPT
const res = await fetchWithPay(url);
if (res.ok) return await res.text();

const status = res.status;
if (status === 402 || status === 403) return { kind: "refused" };      // limit said no
if (status === 503) return { kind: "settle_transient" };               // chain adapter rollout, retryable
if (status >= 500) return { kind: "upstream_error" };                  // the service failed
return { kind: "other", status };

This is illustrative; map the exact statuses your stack surfaces. The point is that the first thing you do with a failure is name it, because the right response follows from the cause, and a single catch-all branch cannot respond correctly to four different problems.

Respond to each kind

Each cause has a sensible response, and none is an immediate identical retry.

A refusal by the spend limit is terminal for that call: do not retry it, surface that the budget was reached, and let the planner choose a different action or stop. An insufficient balance is also terminal until funded: stop, alert that the wallet needs a top-up, and do not loop. A transient settle error (the 503) is the one case that warrants a retry, with backoff and a cap on attempts, because it may clear; if it persists, escalate. An upstream failure is the service's problem, not the payment's, so handle it like any failed API call, a bounded retry if the operation is idempotent, otherwise surface it.

The shape that works is a small switch on the classified kind, each branch doing the right thing, rather than one retry loop wrapped around everything. That structure is what keeps a failure proportionate to its cause.

Never retry blindly

This deserves its own emphasis because it is the single most common way agents misbehave around payments. A blind retry, calling the same paid endpoint again the instant it fails, is wrong for three of the four failure kinds and only sometimes right for the fourth.

Refusals and empty wallets do not change on retry, so retrying is pure waste and looks like a runaway. Even for transient and upstream errors, an immediate unbounded retry hammers the dependency; the correct version is bounded attempts with exponential backoff and a ceiling. Encode this in both the code and the agent's instructions: tell the planner that a failed paid call means stop or change approach, never repeat the identical call. An agent that respects that is calm under failure; one that does not turns every hiccup into a loop.

A retry policy that is safe

For the one failure kind that warrants a retry, the transient settle error, do it safely rather than in a tight loop. Bounded attempts with exponential backoff and a hard ceiling is the whole policy.

TYPESCRIPT
async function settleWithBackoff(call, max = 3) {
  for (let attempt = 0; attempt < max; attempt++) {
    const res = await call();
    if (res.ok) return res;
    if (res.status !== 503) return res;          // only a transient 503 is worth retrying
    await new Promise((r) => setTimeout(r, 500 * 2 ** attempt)); // 0.5s, 1s, 2s
  }
  return { ok: false, status: 503, exhausted: true };
}

Notice what it does not do: it does not retry a refusal or an empty wallet, because those will not change, and it caps attempts so even a persistent 503 cannot loop forever. After the ceiling, it gives up and surfaces the exhausted state so the agent escalates rather than spins. That is the difference between resilient and runaway: a retry that is narrow (only the transient case), backed off (not immediate), and bounded (cannot loop).

Make failures visible

A failed payment the agent hides is worse than one it surfaces. The agent should turn each classified failure into a clear, honest signal, for its own planner and for any human watching.

For the planner, return a structured outcome it can act on, the kind and a short reason, so it can choose to stop, switch approach, or escalate rather than guessing. For humans, log the failure with its classified cause and the identifiers (which call, which agent, what status), so an on-call engineer can see what happened without reconstructing it. And in any user-facing message, be honest about the cause: budget reached, funding needed, or a transient settle issue to retry shortly, never an opaque "something went wrong" that hides whether money was at stake. Visible, classified failures are what let both the agent and you respond correctly instead of treating every problem as the same mystery.

Common pitfalls

Three traps handling failed payments.

One retry loop for all failures. Different causes need different responses. Classify first, then branch.

Treating a 503 as a rejection. The settle 503 during rollout is transient and retryable, not a refusal. Surface it honestly and back off, do not tell the user the payment was declined.

Silent failure. An agent that fails a payment and says nothing, or pretends success, is worse than one that surfaces a clear outcome. Make the failure visible and actionable.

What to ship today

Add a classifier that names a failed paid call's cause from its status, a switch that responds to each kind (stop on refusal, alert on empty wallet, bounded backoff on a transient 503, normal error handling upstream), and an instruction to the planner never to retry an identical failed call. Test each path on Base Sepolia per how-to-test-agent-payments. Pricing is on the pricing page.

FAQ

Frequently asked questions.

Why not just retry a failed payment?

Because most failures will not fix themselves on an identical retry, and some get worse. A payment refused by the spend limit will be refused again; an empty wallet stays empty; retrying just burns turns and, in your logs, looks like a runaway. Only a genuinely transient error (a brief settle hiccup) deserves a bounded retry with backoff. Classify first, retry rarely.

What are the main failure types?

Four. Refused by the spend limit (the call exceeds per_tx_wei or the period allowance). Insufficient balance (the wallet is empty). A settlement error, including a 503 while a network's chain adapter is being rolled out. And an upstream failure, where the paid service itself errored after payment was fine. Each maps to a different response.

How do I tell the failure types apart?

From the response. A non-2xx from the paid call tells you it failed; the status and any logged reason tell you why. On the receive side the adapter names the reason (requirement_mismatch, settle_rejected, and so on). On the pay side you branch on whether the wallet refused (limit), the balance was empty, or the upstream returned its own error after payment.

What is the 503 about?

Outbound settlement is in active rollout, so on a network whose chain adapter is not live yet the settle path can return a 503. Treat it as a transient, retryable condition with backoff, not a permanent failure, and surface it distinctly so you can tell a rollout gap apart from a real refusal. State it honestly in any user-facing message rather than implying the payment was rejected.

Should the agent tell the user about a failed payment?

It should surface a clear, honest outcome rather than silently looping or pretending it succeeded. For a refusal, say the budget was reached; for an empty wallet, say funding is needed; for a transient settle error, say to retry shortly. The planner can then choose a sensible next step, which is far better than an opaque failure.

Create your free agent wallet in 5 minutes.

First payment confirmed in under ten minutes. Free to start.