When an agent overpaid: three real payment risk cases and the controls that would have stopped them

Three anonymized case studies of agent payment failures we've seen — the runaway loop, the wrong-decimals overcharge, and the silent facilitator divergence — and the exact controls that would have caught each one.

We’ve seen a lot of agent payment failures by now. Three patterns account for almost all of the lost money. Here they are, with the control that would have stopped each one.

Case 1 — The weekend runaway

Scenario. An agent that summarizes IT tickets was pointed at a paid weather API by a user who wanted morning reports. Over a Saturday, the agent decided it needed fresh forecasts every 30 seconds. By Monday, $412 had been spent on weather.

Root cause. No per-tool cap. The agent had a daily agent-level budget of $20, but no per-tool/per-endpoint cap, and the LLM’s notion of “how often should I check the weather?” had no ground-truth anchor.

What would have stopped it. A per-endpoint cap:

import { defineBudget } from '@harpd/agent-budget-policy'

const policy = defineBudget({
  perEndpoint: {
    'https://api.weatherapi.com/v1/current.json': { usd: 1.0, window: '1d' },
  },
})

$1/day per endpoint is the hinge: it doesn’t constrain normal use, but it makes runaway loops cost-bound instead of time-bound. Harpd also sends an email alert when any single endpoint crosses 50% of its daily cap — the human gets a chance to intervene before Monday.

Case 2 — The wrong-decimals overcharge

Scenario. A merchant integrated x402 against a USDC endpoint. They priced a route at “0.10” USD, intending ten cents. The code passed amount: 0.10 to the settlement call. The chain settled 0.10 whole USDC — $0.10 became $0.10 because the chain doesn’t know about decimals; 0.10 USDC at 6dp is 100000 minor units, but they passed 0.10 and the facilitator interpreted it as 0.10 base units = $0.0000001, which is effectively free. Or, in the opposite and more common bug, they passed 100000 meaning dollars, which the chain took as $100,000.

Root cause. USDC has 6 decimals. The number that goes on the wire is the smallest unit. There is no library in the loop by default — you write the integer.

What would have stopped it. Two layers:

  1. The Harpd SDK’s settlePayment helper takes amount in micro-USDC (the onchain unit) and rejects amounts above $10 per call unless explicitly overridden:
    await settlePayment(harpd, {
      amount: 100000, // explicitly micro-USDC, $0.10
    })
    
  2. The budget policy SDK’s perCall.usdMax defaults to $0.05. Anything above trips a deny locally before the chain is touched:
    defineBudget({ perCall: { usdMax: 0.05 } })
    

You don’t have to remember decimals in your head. The defaults are set against the two bugs we’ve actually seen.

Case 3 — The silent facilitator divergence

Scenario. An agent used a hosted facilitator for settlement. The facilitator reported “confirmed” for 22 minutes while the chain said “pending” (mempool congestion). The agent assumed the payment was done, called the paid API, and got a 402 back — because the server verified onchain. The agent retried with a new payment. Two payments, one resource.

Root cause. Trusting the facilitator’s word over the chain’s. Facilitators are optimistic; the chain is the truth.

What would have stopped it. The after-settlement hook in @harpd/observe verifies onchain by default via Harpd’s Multi-Source Verifier. If the facilitator says “confirmed” but the chain says “pending”, Harpd logs it as settlement_divergence and fires an alert. The agent’s policy engine sees the divergence and refuses to retry until the chain catches up:

// in your x402 client error handler:
if (err.code === '402' && ctx.lastSettlementStatus === 'pending') {
  // Don't retry — wait for the chain.
  await sleep(5000)
  return retry()
}

Harpd’s reconcile job also dedupes these into a single settled event in the ledger, so the merchant isn’t double-counted and the agent isn’t double-charged.

The pattern across all three

Every case had the same shape: an agent was given money and the freedom to spend it, but no ceiling, no sanity check, and no audit. The fixes aren’t clever cryptography. They’re:

  • a per-endpoint cap (case 1)
  • a per-call max + sane decimals (case 2)
  • onchain verification + a no-retry-on-pending rule (case 3)

That’s the entire job of the @harpd/observe + @harpd/agent-budget-policy pair. They’re open source. Use them without Harpd if you want — they work in observe-only mode. Harpd is the hosted layer that turns their events into alerts, dashboards, and the audit trail a CFO asks about.

Next