Skip to content
be-human.ai
← Back to the journal

Testing & reliability

Idempotent AI Agent Actions: How to Prevent Double Refunds

Design write actions so a retried AI agent request never books, refunds or sends the same thing twice. Keys, a ledger and unknown-state rules.

Be Human typographic cover with the title: Idempotent AI Agent Actions: How to Prevent Double Refunds
Conceptual illustration created with AI · Be Human

Idempotent actions are write operations designed so that repeating the same request leaves the outcome unchanged. An agent that issues a refund, books a slot or sends a confirmation should attach a stable operation key to each intended effect, record that key before the side effect runs, and return the original result when the same key arrives again. Without this guard, a timeout, a restarted worker or a redelivered webhook can turn one decision into two payments, two bookings or two emails.

Key takeaways

  • Every write action needs a stable operation key, created once at decision time and reused on every retry.
  • Record the intent and the key before calling the external system, so a crash in the middle does not erase what was attempted.
  • Let the target system enforce uniqueness where it can, and keep your own ledger as the record of what the agent believes happened.
  • Treat an unknown outcome as its own state: check before retrying, and escalate when the check is inconclusive.
  • Replay duplicate cases on purpose in a test environment before the agent holds real authority.

Why retries create duplicate refunds and messages

Retries are normal. Networks drop responses, model calls time out, workers restart, and orchestration layers resubmit work that never reported back. From the agent's side, a request that timed out looks the same as one that failed before reaching the system. For a read, trying again is harmless. For a write, trying again without a guard may execute the action twice.

Agents raise this risk because they decide what to do from text, and the same decision can be reached more than once. A customer message that says "I was charged twice, please refund the second payment" may produce a refund request, then a second request after a timeout, then a third from a re-run of the conversation summary. Each request looks reasonable on its own. Together they are a double refund, and the business owner learns about it from the bank statement.

Give every write a stable operation key

An operation key identifies one intended effect, not one HTTP call. Build it from the business object, the action type and the decision identifier, for example the order identifier, plus "refund-full", plus the ID of the decision that approved the refund. Every retry of that decision then carries the same key. A key generated fresh on each attempt defeats the purpose, because the receiving system treats each attempt as new work.

Create the key when the decision is made, store it with the decision, and pass it through every layer. If the agent runs a loop of tool calls, the key must survive across iterations. Do not generate it inside the tool wrapper, since each wrapper retry would then produce a new key.

Where the external provider supports idempotency natively, use it. Stripe, for example, accepts an idempotency key on POST requests and returns the stored result when the same key is reused with the same parameters. Check each system you connect for an equivalent feature before you design your own scheme, and keep your ledger as a second record even when the provider offers one. Be careful with a key that is reused with different parameters: the correct behavior is to reject the second request, not to merge it silently.

Record intent before the side effect, and check before every write

Most duplicates happen in the gap between "the call was sent" and "the call was confirmed." Close that gap with a ledger entry written before the external call. A useful entry holds the operation key, the action type, the target object, the amount or a content hash, who or what requested it, the time, and a status set to pending. After the call, the status becomes succeeded, failed or unknown, with the external reference when one exists. A ledger that records only successes cannot prevent duplicates, because the dangerous state is pending: it tells the next attempt that something may already have happened.

The ledger does its work through a uniqueness rule on the operation key. A second insert with the same key fails, and the wrapper reads the existing row instead of acting. Before any write, the tool wrapper should follow four branches:

  • No record exists: insert pending, perform the call, then update the status with the result.
  • Record succeeded: return the stored result and do not call the system again.
  • Record pending or unknown: do not call blindly. Query the external system for the effect, such as whether a refund with that reference exists, and resolve the state first.
  • Record failed with a non-retryable reason: stop and report the reason to the owner of the workflow.

Keep the write endpoints out of the agent's direct reach. The agent should call a single tool that enforces these branches, so that no shortcut exists around the ledger.

For the ledger fields and how to keep them useful for later review, see Superintelligent Agent Audit Trail: Fields Every Action Needs.

Decide what happens when the outcome is unknown

An unknown outcome is not a failure. It means the agent cannot prove whether the effect happened. Retrying an unknown refund because the first call probably did not land accepts the risk of paying twice, so the default must be verification, not repetition. The decision guide below is simple enough for an operations leader to approve once and review later:

  • If the external system can be queried by operation key or external reference, the agent queries it and acts on the answer.
  • If the system cannot be queried, the agent does not retry. It opens a review item containing the decision, the key, the ledger entry and the evidence it has.
  • If the amount, recipient or scope is above a limit the business owner has set, the item goes to a person even when the query is conclusive.
  • If the same customer or object has another pending action, both are held until one of them resolves.

Illustrative scenario: a support agent receives a request to refund a duplicate charge. The payment provider call returns a timeout, and the ledger shows the refund as unknown. The agent queries the provider for a refund carrying the operation key, finds one created by the first attempt, records that reference, and tells the customer the refund is already issued. A naive retry would have issued a second refund to the same card.

The agent then escalates only what it cannot resolve. For a clean way to hand over the decision and not the raw log, see Human escalation for AI agents: send the decision, not the mess.

Cover messages, bookings and webhooks, not only payments

Duplicate emails, SMS, WhatsApp messages, calendar invitations and purchase orders need the same discipline. Money has a balance that finance can reconcile, but a customer who receives two confirmations notices immediately, and that damages trust faster than most back-office errors. Use an operation key per recipient, per message type, per decision, and allow the wrapper to send that message only once per key, even when the call is retried.

Webhooks deserve their own rule. Payment and scheduling platforms can deliver the same event more than once, and a receiver that acts on every delivery will repeat work. Stripe documents how webhook events are delivered and how receivers should verify them. Store each processed event identifier in the ledger, verify the signature before acting, and skip events already recorded as processed. Treat the event identifier as the operation key for the work it triggers.

Bookings have a subtler failure. A booking request that is retried after a timeout may create a second slot that the calendar accepts because it is not an exact duplicate. Include the customer, the service, and the time window in the key, and have the wrapper check for overlapping pending or confirmed items before it writes.

Replay duplicates before the agent gets authority

Before an agent is allowed to perform live writes, test its wrappers against deliberate duplicates. Write the expected outcome for each case in advance, then run them in a staging environment that behaves as closely as possible like the live provider. A short replay set usually covers these cases:

  • The same request sent twice in sequence, where the second call must return the stored result.
  • A request whose first call is simulated to time out, where the wrapper must query before retrying.
  • The same webhook event delivered twice, where the second delivery must do nothing.
  • Two requests with the same key but different parameters, where the second must be rejected.
  • Two requests for the same object sent close together, where only one must succeed.

Record the results and keep the replay set. It becomes a regression check for every change to the tool wrapper, the prompt, or the connected system. For the separation between test and live environments, see AI Agent Staging Environment: Why You Need Both Test and Live.

In Be Human's managed model, the operation key scheme, the ledger and the unknown-state rules are defined during the first engagement, as part of the memory, tools and approval boundaries that the agent operates under. Recurring work then covers monitoring for duplicate keys and for pending items older than the agreed window, periodic permission review of the connected systems, and a documented incident path for any case where a double effect may have occurred. The business keeps the decision about which actions the agent may take on its own, and changes to those actions go through the same review.

If you are unsure which of your workflows would benefit most from this discipline, the free Be Human AI diagnostic can help you find the first task worth handing to an agent.

Frequently asked questions

Can the language model avoid duplicate actions by reading the conversation history? Not reliably. The model may summarize, reword or repeat a request after a timeout, so the same decision can arrive with different text and no shared identifier. The protection has to live in the tool layer, where a stable key and a ledger decide whether the effect already happened.

Is an idempotency key the same as a retry limit? No. A retry limit controls how many times the agent tries, while an idempotency key controls whether repeated attempts can produce a second effect. You usually need both, and the retry policy should check the ledger before every attempt.

Do I need idempotency if my provider already rejects duplicates? Yes, in most cases. Provider rules often cover exact duplicates only, and they may not cover near-duplicates such as two bookings for the same customer with a slightly different time. Your ledger also gives you a record of what the agent attempted, which the provider does not keep for you.

What should an operations leader ask a team before granting an agent write access? Ask how each write is keyed, what gets recorded before the call, what the agent does when the outcome is unknown, and who reviews the items that cannot be resolved automatically. If the team cannot answer these questions with specifics, the agent is not ready for live authority.

Your next step

See where an AI agent fits in your company first.

Take the free Be Human AI diagnostic. A few questions about your work show which task to hand to an agent first, what it needs to know and where a person approves.

Be Human editorial guidance. Examples are illustrative unless explicitly attributed. Sources provide background; implementation choices depend on your workflow.

Less busywork. More room for you.

Bring us the work you wish you had time for.

Book a call to try a personalized AI agent free for 7 days ↗Or take the free AI diagnostic →