Your AI agent may work fine today, and tomorrow it could burn hundreds of USDT through duplicate payments without you knowing.
Last week, a friend showed me his agent logs. A simple on-chain data fetch task hit a network hiccup, the agent retried 3 times, and x402 charged him 3 times—but he only received the data once. He said: "I set a per-payment limit. Each charge was only 0.5 USDT, so how did I lose money?" The problem is not the single amount. It is the number of attempts. An agent stuck in a retry loop can send dozens or hundreds of small payments per minute. The total can burn your budget within minutes.
x402 has no refund mechanism at the protocol level. Once a payment is successful, the money does not come back. So you need idempotency: doing the same thing 100 times should only charge once.
Why agents often get charged repeatedly
First understand how money gets charged twice before you act.
Scenario:
- Agent sends a request, and the server returns 402 (payment required).
- Agent signs, transfers, and retries with payment proof.
- Server accepts the payment and returns data.
- Network drops, and the agent does not receive the response, so it thinks the request failed.
- Agent sends the request again—and goes through the full payment flow again.
The key issue: each x402 payment proof is one-time. If the agent does not remember that a payment already succeeded, it will create a new proof and get charged again.
Two core concepts:
Idempotency Key: a unique string that represents the specific task. For example,
task_123:get_eth_price:v1. The agent sends it with the first request, and the server records that this key has already paid. On retry, the server sees the same key and returns the previous result without charging again.Circuit Breaker: monitors spending speed, not single amounts. If the agent spends more than a threshold within a short time (for example, 1 minute), it pauses payment and sends an alert. This stops retry loops from quietly burning money.
Compare three ways to prevent duplicate charges
| Solution | Best for | Effort | Protection level |
|---|---|---|---|
| Idempotency Key (application layer) | You have your own backend and can change code | Low | Prevents duplicate charges for the same logical task |
| Built-in SDK idempotency (e.g. StableOps Agent SDK) | Using an existing agent payment framework | Very low | SDK handles retry idempotency automatically |
| Replay detection middleware (e.g. Presidio Replay Guard) | High security, multi-instance deployment | Medium | Uses payment fingerprints to block replays and remove duplicates across processes |
References:
- Idempotency key pattern: a common engineering approach for HTTP x402 micropayments
- StableOps SDK: supports the
idempotencyKeyparameter and reuses the signature on retries - Presidio Replay Guard: supports cross-process deduplication with Redis and configurable TTL
Next step: three lines of code solve 80% of the problem
If you use Node.js and control your own request logic, the simplest way is:
Step 1: Generate an idempotency key for each paid action
const idempotencyKey = task_${taskId}:action_${actionType}:v1Step 2: Send it with the request
If you use the StableOps Agent SDK:
const result = await payments.x402Fetch('https://api.example.com/paid', {idempotencyKey: 'task_123:paid-resource:v1'})If you write it yourself:
- Send
Idempotency-Key: xxxin the request header or body - The server uses this key to decide whether the request has already been processed
Step 3: Server records settled idempotency keys
if (await ledger.has(idempotencyKey)) {
return ledger.get(idempotencyKey) // return the stored result, no charge
}
// normal payment flow
await ledger.record(idempotencyKey, receipt)How to know it works: when retrying the same request, the server returns 200 OK instead of a new 402, and the wallet has no extra charge record.
Different situations
Situation A: You use StableOps or a similar framework
- Pass the
idempotencyKeyparameter when callingx402Fetch. The SDK automatically handles retry signature reuse.
Situation B: You built your own x402 client
- Maintain your own storage for settled idempotency keys (Redis or database). Check this storage before retrying. If the key exists, return the stored result.
Situation C: You are the API provider (called by agents)
- Implement an idempotency key check on the server. When a request arrives, first check
Idempotency-Key. If it has already been processed, return the cached result instead of charging again. Rootstock's documentation suggests using Redis to store used transaction hashes with a TTL of 30 days.
High-risk reminder: a single-payment limit cannot stop a retry loop. An agent sending 60 requests per minute at 0.5 USDT each stays within the per-payment limit, but it can burn 1,800 USDT in an hour. You must add spending rate monitoring.
How to verify the fix
Run a test: make the agent execute a task that triggers retries (for example, intentionally make the target service fail for a few seconds, then recover). Observe:
- How many times did the wallet get charged?
- Does the
idempotencyKeyin the logs repeat and return a cached result? - If a circuit breaker is enabled, did the spending speed trigger a pause?
Verification channel: check the agent audit logs or the wallet transaction history. There should be exactly one payment record with settled status for this idempotencyKey.


