A timeout, then a retry
The first refund went through, the response was lost, and the agent sent it again. The customer is refunded twice.
Infrastructure for AI agents
Your AI agent decides a customer deserves a refund. Onceproof checks it against your policy, sends it to Stripe exactly once, confirms what Stripe actually did, and signs a receipt. When the answer is uncertain, it says so, and never retries blindly.
Today: Stripe refunds and customer balance credits. Nothing else, done carefully.
The problem
A refund request that times out may or may not have happened. Logs record what the agent tried, not what Stripe did.
The first refund went through, the response was lost, and the agent sent it again. The customer is refunded twice.
Stripe accepted the refund, then it failed at the bank. The agent already told the customer it was done.
Finance, an auditor or the customer asks. There is no record anyone can check independently.
How it works
Your agent never holds the Stripe key. It calls Onceproof, which holds a restricted key and owns execution.
Unknown is not failed
When Stripe doesn't answer, the refund is marked Unknown. New refunds on that charge are blocked, and Onceproof looks the refund up in Stripe by its own id. It re-sends only if the refund is proven absent. Most unknowns resolve in seconds; the rest go to a person with the evidence.
This panel uses the same component operators see in the console.
We cannot yet confirm whether this refund exists in Stripe. It is not treated as failed, and it will not be retried until the external state is known.
Verification
After Stripe answers, Onceproof reads the refund back and compares it with what was asked. A mismatch goes to a person; nothing is called "verified" until Stripe shows it.
Signed receipts
Every verified refund gets a receipt signed with Onceproof's key: what was asked, who approved it, what Stripe shows. Change one character and the check fails. Give it to finance, an auditor or the customer.
Control
Limits and approval thresholds live outside the model. Above a threshold, a person approves the exact amount; the server re-checks state, policy and parameters when they click.
Already in production? Start in shadow mode: report the refunds your agent already makes, and Onceproof finds orphans, ghosts and duplicates without touching execution.
Developers
Call Onceproof instead of Stripe from your agent, or give Claude the MCP tools. The result tells your code, or the model, exactly what happened and whether it may act again.
from onceproof import Client, business_key
aa = Client(OP_URL, api_key=AGENT_KEY)
r = aa.refund(charge_id="ch_3Q9xYz", amount=4900, currency="usd",
business_key=business_key("zendesk", ticket_id, "ch_3Q9xYz"))
if r.done: reply("Your refund is on its way.")
elif r.needs_human: reply("A teammate is confirming it.")
elif r.in_flight: reply("It's being confirmed; no need to ask again.")
{
"mcpServers": {
"onceproof": {
"command": "onceproof-mcp",
"env": { "OP_URL": "https://…", "OP_API_KEY": "op_live_…" }
}
}
}
# tools: refund_charge · credit_customer_balance
# get_action_status · get_receipt
POST /v1/actions
Authorization: Bearer $AGENT_KEY
{ "action_type": "stripe.refund.create",
"target": { "charge_id": "ch_3Q9xYz" },
"params": { "amount": 4900, "currency": "usd" },
"business_key": "zendesk:48213:ch_3Q9xYz" }
→ { "id": "act_…", "state": "unknown", "retry_safe": false }
Security and control
A restricted key stays inside Onceproof. Live keys are refused unless you opt in.
Stop any agent instantly; resuming needs an admin.
Viewers, approvers and admins. Approvals are recorded as the signed-in person.
Every state change is hash-linked; the console recomputes the chain.
Ed25519 receipts; HMAC-signed events to your systems, private addresses refused.
Stripe refunds and credits only. No single sign-on or MFA yet; we say so.
Open the demo, press “Simulate lost Stripe response”, and watch the refund go from Unknown to Verified without being sent twice.