Skip to main content
Billing is the highest-risk surface for AI agents. A retry after a dropped connection can charge a customer twice, and an agent re-running its plan can do the same. Invoke makes each charge happen at most once, and it tells you when it can’t know.

Name the charge

Build the idempotency key from what makes the charge unique, usually the invoice. The same key is the same charge:
If your retry loop, a restarted worker or the agent itself sends it again, the call resolves with status: "duplicate" and the original receipt. No second charge is made.

When the answer is lost

If the connection drops after Stripe got the request, nobody knows yet whether the charge landed, and a charge isn’t safe to re-run blindly. So Invoke doesn’t guess. It holds the charge as unknown, and call() throws OutcomeUnknownError with safeToRetry: false. A retry with the same key is refused, not re-run.
Whoever checks Stripe settles it with an account key:
happened commits the charge and seals its receipt. not_happened releases the reserved budget, so an honest retry can run.

Put big charges behind a human

A workspace policy can require approval above an amount:
A charge over the line throws ApprovalRequiredError, and nothing is charged. Pass waitForApproval to wait for the reviewer instead. The receipt records who approved it.

Verify the customer before a refund

Before a refund or any follow-up charge, confirm the customer hasn’t changed since the agent resolved it. POST /state/verify compares what the agent assumed with what is true now:
It answers "status": "verified" when the state still matches, and "status": "replan_required" with the mismatched fields when it doesn’t. Act only on verified.

Next steps