Answers · AI evidence
How do I bind an AI agent's payment receipt to what triggered it, the policy that allowed it, the payment details and its idempotency key, and reconcile against the provider before replaying a run?
Last updated
TL;DR: Write one receipt per payment that ties four things together: the trigger (the task, step and input that led the agent to pay, by id and hash), the policy decision (policy id and version, allow or deny, approval id), the payment itself (amount in minor units, currency, payee, provider and provider payment id) and the idempotency key the agent sent. Hash the canonical receipt, sign or chain it, and put the same idempotency key and receipt id in the payment's metadata so the two sides point at each other. Before you replay or resume a run, reconcile: for every payment step in the recorded run, look up the provider by idempotency key or metadata and compare status, amount, currency and payee with the receipt. Steps that already succeeded are replayed from the receipt, not paid again; mismatches and unknowns stop the replay and go to a person.
What should I check, step by step?
1. Give each payment step a stable receipt id and idempotency key, created when the agent commits to paying and saved before the first call to the provider.
- Record the trigger: task id, step id, the message or event that caused the payment, and a hash of the inputs the agent used (invoice, quote, Payment Required body).
- Record the decision: policy id and version, the allow or deny and its reason, the approval id, approver and expiry if one was needed, and the action hash the approval covered.
- Record the payment: provider, provider payment id (PaymentIntent, charge, transaction hash), amount in minor units, currency, payee, status and the time of the last status you saw.
- Hash the canonical receipt (RFC 8785 JSON or a fixed field order) and chain or sign it, so a later edit to any field shows.
- Write receipt_id and the idempotency key into the payment's own metadata or reference field, so you can find the payment from the receipt and the receipt from the payment.
- Before replaying or resuming, run a reconcile pass over every payment step: fetch the provider record, compare it with the receipt, and label each step matched, missing, mismatched or unknown.
- Replay matched steps from the receipt, retry missing steps only with the same idempotency key and a still-valid approval, and stop on mismatched or unknown.
Why does an agent payment receipt need the trigger, policy, payment and idempotency key?
Trigger without payment details tells you why the agent wanted to pay, but not what actually left the account.
- Payment details without the policy decision tell you what was paid, but not which rule or which person allowed it, which is what a reviewer or auditor asks first.
- The idempotency key is what joins your record to the provider's. Stripe returns the saved result for a repeat request with the same key and errors if parameters change under it; the IETF Idempotency-Key draft says a key must not be reused with a different payload. Without the key in the receipt you can't prove a replayed call was the same payment.
- With all four, any one of them can be checked against the others: the amount against the approved action hash, the payment against the key, the trigger against the recorded inputs.
How do I reconcile agent payments before replaying a run?
Durable-execution tools replay recorded results so finished work isn't redone: Temporal replays a workflow from its event history, and LangGraph's durable execution saves task results so they're reused when a run resumes. A payment that happened outside that record, or whose result was lost in a crash, needs an explicit check.
- Look payments up by id or by your own reference. Stripe lets you search Charges and PaymentIntents by metadata, but its docs say not to use search for read-after-write flows because new data can take up to about a minute to appear; for a payment just made, retrieve it by id or use the list APIs.
- Compare status, amount, currency and payee with the receipt. Treat a timeout or an unknown status as unknown, not failed, and don't retry until you've found out what happened.
- Write the reconcile result into the run record (step, receipt id, provider status, verdict, checked_at), so the replay itself leaves evidence of what it found.
What should happen to each payment step after reconciliation?
Matched: the provider shows the payment as the receipt describes. Replay returns the recorded result; no new call to the provider.
- Missing: no payment exists under that key or reference. Retry with the same idempotency key, and only if the approval hasn't expired and the action hash still matches.
- Mismatched: the provider shows a different amount, currency, payee or status (for example refunded). Stop the replay and send it to a person; something changed outside the run.
- Unknown: the provider couldn't be reached or the status is still pending. Wait and check again; don't pay.
What are the common failure modes?
The idempotency key is generated inside the HTTP call, so the replay sends a new key and the provider happily charges again.
- Stripe idempotency keys can be pruned after 24 hours, so a replay days later can't rely on the key alone; it needs the reconcile lookup by payment id or metadata.
- The receipt stores the amount the agent intended, not the amount the provider confirmed, so currency conversion or fees make every check fail or, worse, pass silently.
- The reconcile step uses search immediately after paying and finds nothing, then retries.
- Receipts are written to the agent's own log folder, where a later step can edit or delete them.
How do I ask Rightshold about this (free, no key)?
Add the MCP server https://www.rightshold.com/mcp to Claude, Cursor, ChatGPT, Grok or VS Code, then ask: “Record a signed receipt for this agent payment: the trigger and inputs by hash, the policy decision and approval id, the provider payment id, amount, currency and payee, and the idempotency key.”
{
"tool": "record_ai_run",
"arguments": {
"model": "<model or agent id that made the payment>",
"provider": "<runtime or framework>",
"prompt_sha256": "<sha256 of the trigger event + inputs the agent used>",
"output_sha256": "<sha256 of the canonical payment receipt>",
"params": {
"task_id": "task_8841",
"step_id": "pay-3",
"policy_id": "pol_…",
"policy_version": 4,
"decision": "allow",
"approval_id": "apr_…",
"amount_minor": 2000,
"currency": "USD",
"payee": "api.vendor.example",
"payment_provider": "stripe",
"payment_id": "pi_…",
"idempotency_key": "task_8841-pay-3"
},
"ran_at": "2026-11-07T07:00:00Z"
}
}- Needs an API key. Only hashes and the fields you send are recorded; the receipt is Ed25519-signed and written to Rightshold's append-only ledger.
- verify_ai_run (free, no key) checks a receipt's signature and, given the original text, that it matches the recorded hashes. Run it in your reconcile pass before trusting a receipt for replay.
- For the allow/deny itself, authorize_spend returns a signed decision against your spend policy; put its id in the receipt's params.
Rightshold doesn't process payments or query your payment provider. It signs and timestamps the receipt fields and hashes you send; the reconcile lookup against Stripe, a wallet or a bank is your code. Not legal or financial advice.
What are the sources?
- Stripe API: Idempotent requests
- IETF: The Idempotency-Key HTTP Header Field (draft-ietf-httpapi-idempotency-key-header)
- Stripe Docs: Search (metadata queries; data freshness, not for read-after-write)
- Stripe Docs: Metadata (PaymentIntent to Charge snapshot; metadata in webhook events)
- Temporal docs: Events and Event History
- LangGraph: Durable execution (tasks for side effects and non-determinism)
- RFC 8785: JSON Canonicalization Scheme
Related
- How to stop an AI agent from paying twice when it retries
- How to replay an AI agent run deterministically from a recorded call order
- How to record the inputs an agent used for a payment or identity decision
- Verified AI Runs
Not legal advice. JSON · Markdown · All answers
Get a free API key in one call
No card and no email needed. 100 free credits; the license and policy checks stay free without a key.
curl -s -X POST https://www.rightshold.com/v1/accounts/signup -H "Content-Type: application/json" -d '{"accept_terms":true}'
export RIGHTSHOLD_API_KEY=rs_... # the api_key from the reply (shown once)
curl -s https://www.rightshold.com/v1/licenses/check -H "Authorization: Bearer $RIGHTSHOLD_API_KEY" \
-H "Content-Type: application/json" -d '{"license":"MIT","intended_use":{"commercial_use":true}}'
