{
  "question": "How do I stop an AI agent from paying twice when it retries a request?",
  "slug": "how-to-stop-an-ai-agent-from-paying-twice-when-it-retries",
  "url": "https://www.rightshold.com/answers/how-to-stop-an-ai-agent-from-paying-twice-when-it-retries",
  "short_answer": "Give every purchase one idempotency key, created once before the first attempt and sent unchanged on every retry, and use APIs that honour it. POST is not idempotent in HTTP, so a timeout followed by a retry can create a second charge unless the server deduplicates. Stripe accepts an Idempotency-Key header of up to 255 characters on POST requests and returns the saved result for a retry with the same key; keys can be pruned after 24 hours. The IETF Idempotency-Key header draft says a key MUST NOT be reused for a different request payload. For x402, a signed EIP-3009 authorization can settle only once because its nonce is used up on chain, but an agent that signs a fresh authorization on retry can pay again; x402's payment-identifier extension adds an id the client reuses on retries so servers and facilitators can return the cached response. Before retrying a payment that timed out, check whether the first one settled.",
  "checklist": [
    "Create the idempotency key when the agent decides to buy, store it with the order (a random UUID v4 is fine), and reuse it on every retry of that purchase. Never generate a new key inside the retry loop.",
    "Send it the way the API expects: Idempotency-Key header for Stripe and APIs following the IETF draft; extensions[\"payment-identifier\"].info.id for x402 servers that advertise payment-identifier.",
    "Keep the request body identical on retries. Stripe errors if the parameters differ from the first request with that key; the IETF draft says to answer 422 for a reused key with a different payload.",
    "Treat 409 Conflict as 'the first attempt is still running': wait and retry with the same key, don't create a new one.",
    "For x402, keep the signed payment and resend the same one rather than signing a new authorization. EIP-3009 nonces can be used only once, so the same authorization can't be settled twice.",
    "After a timeout, check before paying again: read PAYMENT-RESPONSE if you got one, look up the transaction hash on chain, or list recent charges. An x402 settlement_pending error means the transaction may still confirm.",
    "Make resumable agent runs safe too: in LangGraph code before interrupt() runs again on resume, so put payments after it or make them idempotent.",
    "Cap total spend per order and per day, so a bug that does pay twice is bounded."
  ],
  "sections": [
    {
      "heading": "Why retries double-charge",
      "points": [
        "HTTP defines GET, HEAD, PUT and DELETE as idempotent and POST and PATCH as not. When a POST payment times out, the client can't tell whether the server never got it or charged and the reply was lost. Retrying without a key risks a second charge; not retrying risks no charge.",
        "Agents retry more than people do: frameworks retry failed tool calls, models call the same tool again when output is unclear, and durable runs replay steps after a crash. Each of these can repeat the payment unless the payment carries a stable key."
      ]
    },
    {
      "heading": "How the main mechanisms behave",
      "points": [
        "Stripe (API v1): send Idempotency-Key on POST requests, up to 255 characters. Stripe saves the status code and body of the first request once execution begins and returns them for retries with the same key, including 500 errors. Keys can be removed once they're at least 24 hours old, after which the same key makes a new request. Stripe suggests V4 UUIDs and says not to use personal data as keys.",
        "IETF draft-ietf-httpapi-idempotency-key-header (version 08, October 2026, still an Internet-Draft): Idempotency-Key is a Structured Field string; a key must be unique and must not be reused with a different payload. A server should answer 400 if a required key is missing, 422 if a key is reused with a different payload, and 409 if the original request is still being processed.",
        "x402: EIP-3009 authorizations carry a random 32-byte nonce, and the token contract refuses a nonce that was already used, so the same signed payment can't settle twice. The payment-identifier extension adds a client id (16 to 128 characters): same id and same payload returns the cached response, same id with a different payload returns 409, and a server that sets required: true answers 400 without an id. Servers should bind the id to the scheme, network, asset, amount, payTo and resource."
      ]
    },
    {
      "heading": "What can go wrong",
      "points": [
        "A new key per attempt: the most common bug. If the key is generated in the function that sends the request, every retry looks like a new purchase.",
        "Reusing one key for different purchases: the second purchase silently returns the first one's result, or fails. One key per intended charge.",
        "Retrying after the key expired: after Stripe prunes a key (24 hours or more), the same key makes a new charge. Don't replay old payments; reconcile first.",
        "Signing a new x402 payment on every retry: the nonce protects only that one authorization. Resend the same signed payment, or check the chain first."
      ]
    }
  ],
  "checks": [],
  "ask_rightshold": {
    "start": "https://www.rightshold.com/start?ref=seo",
    "mcp_endpoint": "https://www.rightshold.com/mcp",
    "ask": "Before you pay, check this purchase against my spending limits and get a signed allow or deny, with the order's idempotency key as the reference.",
    "mcp_call": {
      "tool": "authorize_spend",
      "arguments": {
        "credential_id": "<your agent credential id>",
        "amount_minor": 2000,
        "currency": "USD",
        "merchant_domain": "api.example.com",
        "reference": "order-7f3c2a90-idem-key"
      }
    },
    "also": [
      "Needs your API key. authorize_spend checks the purchase against the per-purchase and daily caps you set with set_spend_policy and returns a signed allow or deny. It records decisions only and never moves money. Each allow counts toward the daily total, and it doesn't deduplicate by reference, so a repeated call shows up against the cap.",
      "REST, free: POST /v1/x402/price-tag with {payment_required_header} or {body} lists each payment option and reports whether the server advertises the payment-identifier extension (and whether it's required), plus the amount in atomic units and in dollars for Circle USDC.",
      "request_human_approval binds a person's yes to a hash of the exact purchase, so a second charge needs a second approval."
    ],
    "scope": "Rightshold reads x402 payment requirements and records spending decisions against limits you set. It doesn't hold funds, send or retry payments, or deduplicate charges for you; the payment provider or the server has to honour the idempotency key."
  },
  "sources": [
    {
      "title": "Stripe API: Idempotent requests",
      "url": "https://docs.stripe.com/api/idempotent_requests"
    },
    {
      "title": "IETF: The Idempotency-Key HTTP Header Field (draft-ietf-httpapi-idempotency-key-header)",
      "url": "https://datatracker.ietf.org/doc/draft-ietf-httpapi-idempotency-key-header/"
    },
    {
      "title": "RFC 9110: HTTP Semantics, 9.2.2 Idempotent Methods",
      "url": "https://www.rfc-editor.org/rfc/rfc9110.html#name-idempotent-methods"
    },
    {
      "title": "x402 extension: payment-identifier (coinbase/x402)",
      "url": "https://github.com/coinbase/x402/blob/main/specs/extensions/payment_identifier.md"
    },
    {
      "title": "x402 protocol specification v2: replay protection, settlement_pending",
      "url": "https://github.com/coinbase/x402/blob/main/specs/x402-specification-v2.md"
    },
    {
      "title": "ERC-3009: Transfer With Authorization",
      "url": "https://eips.ethereum.org/EIPS/eip-3009"
    },
    {
      "title": "LangGraph: Interrupts (side effects before interrupt must be idempotent)",
      "url": "https://docs.langchain.com/oss/python/langgraph/interrupts"
    }
  ],
  "related": [
    {
      "title": "How to require human approval before an AI agent spends money",
      "url": "https://www.rightshold.com/answers/how-to-require-human-approval-before-an-ai-agent-spends-money"
    },
    {
      "title": "How to read an x402 402 Payment Required response as a price",
      "url": "https://www.rightshold.com/answers/how-to-read-an-x402-payment-required-response-as-a-price"
    },
    {
      "title": "How to log every AI agent tool call for audit",
      "url": "https://www.rightshold.com/answers/how-to-log-every-ai-agent-tool-call-for-audit"
    }
  ],
  "computed_from": "Curated guidance; license verdicts computed from Rightshold's license profiles at render time. Not legal advice.",
  "updated": "2026-10-30"
}