LetspingLetsPing

AI agent made a refund without approval

Immediately revoke the agent’s ability to call refund or payout tools, reconcile what changed in your payment provider, and add a deterministic approval gate in front of dangerous tools so no refund can be executed without a signed decision. This page covers immediate containment and how to design a safe approval flow around Stripe or other billing tools using LetsPing so the same incident cannot repeat.

1. Triage: contain the damage

  1. Rotate or revoke any API keys the agent used for refunds or payouts.
  2. Use your payment provider dashboard to list refunds and payouts in the last few hours or days.
  3. Reconcile against your own ledger so you know exactly which transactions were triggered by the agent.
  4. Temporarily disable autonomous refund flows until you have an approval step in place.

2. Why this happened

The core problem is that the agent had direct authority to call a refund tool with no deterministic gate in front of it. If the prompt, tools, or model nudged it toward issuing goodwill refunds, there was nothing between that decision and live money movement.

Fixing prompt text or adding more logging does not change that fundamental shape. You need an always-on guard at the network boundary that decides whether a given refund payload is allowed to execute.

3. Add a human and firewall in front of refunds with LetsPing

With LetsPing, the agent never calls Stripe or your billing API directly. Instead it calls lp.ask() or lp.defer() with a payload like { user_id, amount, currency }. LetsPing pauses the workflow, shows the payload in the dashboard or mobile app, and only after a human approves does your backend actually call Stripe.

import { LetsPing } from "@letsping/sdk";

const lp = new LetsPing();

const decision = await lp.ask({
  service:  "billing-agent",
  action:   "stripe_refund",
  priority: "high",
  payload:  { user_id: "u_123", amount: 550, currency: "usd" },
});

if (decision.status === "APPROVED" || decision.status === "APPROVED_WITH_MODIFICATIONS") {
  const refundPayload = decision.status === "APPROVED_WITH_MODIFICATIONS"
    ? decision.patched_payload
    : decision.payload;
  await stripe.refunds.create(refundPayload);
}

The decision object is signed and auditable. If something goes wrong later, you can prove exactly who approved which refund and what the payload looked like at the time.

4. Make this pattern the default for any destructive tool

Refunds are just one example. The same pattern applies to payouts, subscription changes, promotions, Terraform runs, or any other tool that can move money or mutate infrastructure.

  • Wrap every destructive tool behind LetsPing.
  • Keep read-only tools and analysis tools direct if you want low latency.
  • Log all approvals and rejections so you can satisfy audit and chargeback investigations.

5. Related guides

For a deeper walkthrough of production-grade approval flows, see Securing LangGraph in Production and How to Pass InfoSec with Autonomous Agents.