Name the charge
Build the idempotency key from what makes the charge unique, usually the invoice. The same key is the same charge: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, andcall() throws OutcomeUnknownError with safeToRetry: false. A retry with the same key is refused, not re-run.
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: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:
"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
- TypeScript SDK: every error and what it means
- Reconciliation: how Invoke handles unknown outcomes
- POST /state/verify: the full API reference