If agents are going to act on the open internet, they will eventually hit a price tag.
That changes the design problem. Calling an API is easy compared with letting a non-human actor spend money without handing it private keys, unlimited budget, or a prompt-level spending rule it can be talked out of.
The thesis: payment is not a tool call. It is a governed financial action.
That means the payment path needs more than a model deciding "yes" or "no." It needs user consent, credential isolation, session budgets, policy checks, telemetry, and a narrow proof path that the model cannot bypass. The useful primitive is not "agent calls payment API." The useful primitive is "agent can pay for a bounded resource without owning the wallet."
I tested that pattern with Amazon Bedrock AgentCore Payments, Coinbase CDP, x402, Cedar policy enforcement, CloudWatch, and a Strands agent plugin. The result is strong enough to change the mental model, with one caveat: this is testnet evidence, and not every x402 endpoint accepted the same generated proof.
What The Payment Primitive Owns
The clean architecture separates four jobs:
| Layer | Job |
|---|---|
| Paid resource | Returns HTTP 402 with price, token, network, and payTo address. |
| Agent/tool | Encounters 402, extracts the payment requirement, and asks for proof. |
| Payment manager | Generates the x402 payment proof without exposing wallet secrets to the model. |
| Governance | Enforces user consent, session budget, policy allow/deny, and observability. |
That split matters. If the model sees private keys, the design is already broken. If the spending limit is prompt text, the design is also broken. The payment boundary has to sit outside the model context.
In the experiment, the AgentCore side owned the payment manager, connector, instrument, session, and proof generation. Coinbase CDP owned the delegated wallet/signing path. x402 provided the HTTP 402 challenge and proof format. Cedar controlled whether the paid action was allowed before the payment path ran.
The key detail: the model never needed the payment credential. It only needed a tool response that said, effectively, "this resource costs X on Y network." The proof came from the payment infrastructure.
The First Proof: HTTP 402 To HTTP 200
The base flow worked.
The test created a payment manager, Coinbase CDP credential provider, connector, active payment instrument, and short-lived payment session. The session had a 0.10 USD budget and a 30 minute TTL. The wallet was funded with Base Sepolia testnet USDC, and WalletHub permission was granted for the app to pay from that wallet.
Then the tool called a paid endpoint. The endpoint returned HTTP 402 with an x402 version 2 payment requirement on Base Sepolia. AgentCore generated a PAYMENT-SIGNATURE proof through the ProcessPayment API. The retry against the AWS-documented endpoint returned HTTP 200.
That proves the core primitive: an agent-side tool can hit a paid resource, receive a price challenge, generate proof through infrastructure, and retry successfully without the model touching the wallet.
I also ran a controlled two-sided merchant test. Instead of paying a sample endpoint, I created a separate merchant wallet and ran a local x402 endpoint that advertised that wallet as payTo. The paid retry returned HTTP 200, the buyer wallet decreased, and the merchant wallet increased by 0.001 testnet USDC.
That second test matters because it proves both sides of the flow: the buyer can generate proof, and the merchant can receive value.
Budget Is Not A Prompt Instruction
The most important test was not whether a payment could succeed. It was whether a payment could be stopped.
I created a session with a 0.02 USD budget and used a local paid endpoint priced at 0.01 testnet USDC. AgentCore generated two payment proofs in the session. After those attempts, the session showed 0 USD available spend. The next same-session proof request failed with InsufficientBudget.
That is the right failure mode.
The agent did not need to remember the budget. The prompt did not need to say "please do not spend more than two cents." The model could be instructed to ignore limits and the infrastructure would still reject the over-budget payment.
This is the deeper point: financial constraints belong in mechanisms, not model behavior. Prompts are useful for intent. They are the wrong place to enforce spend.
Cedar Before Payment
Budgets answer "how much can this session spend?" They do not answer "who is allowed to invoke this paid action with these arguments?"
That is where policy belongs.
I reused a live AgentCore Gateway with a Cedar policy engine in enforce mode. A valid user token called a governed action with two amounts:
| Request | Result |
|---|---|
45.0 |
Allowed |
600.0 |
Denied with JSON-RPC -32002 before backend execution |
During the denied-call window, the Payment Manager logs showed zero new Payment processed events and CloudWatch showed zero new SpendAmount datapoints.
That proves the first control: policy can stop the workflow before the payment path starts.
Then I hardened the shape. A runner first called Gateway/Cedar with a small amount. If Cedar allowed it, the runner invoked the real x402 payment path. If Cedar denied it, the runner did not attempt payment. The allowed branch generated payment proof; the denied branch made no payment attempt.
Finally, I moved the paid-fetch implementation itself behind Gateway as a Lambda target. Cedar allowed amount < 0.01, denied the over-limit branch, and the Lambda entered AgentCore Payments only after policy allow.
The Gateway-hosted run proves the full chain: Cedar allow, proof generation inside the Lambda, denial before Lambda/payment for the over-limit request, and a settled 200 on the allowed branch's retry.
The architecture is still the right one:
Gateway policy allow/deny -> paid tool execution -> AgentCore payment proof -> retry paid resource
The policy decision happens before money can move. The payment session budget still applies after policy allow. Those are separate controls, and they should stay separate.
Observability Is Part Of The Product
Payment systems without audit trails are demos.
AgentCore Payments publishes metrics into AWS/Bedrock-AgentCore — SpendAmount, OperationSuccess, OperationFailure, OperationLatency, ActiveSessions, PaymentTokenFetchSuccess, PaymentTokenFetchFailures — with SpendAmount carrying ProcessPayment datapoints per Coinbase connector and payment manager. Vended log delivery for the payment manager produces lifecycle events for session creation, instrument retrieval, and payment processing, all in your own CloudWatch log group.
Metrics and logs are one delivery pipeline. X-Ray span correlation is a second, entirely separate one, and it is easy to miss because nothing about ProcessPayment failing to appear in X-Ray looks like a missing configuration step — it just looks like empty search results. Log delivery uses logType=APPLICATION_LOGS pointed at a CWL (CloudWatch Logs) destination. Spans require their own delivery source with logType=TRACES, pointed at an XRAY destination:
aws logs put-delivery-source \
--name "payments-traces-source" \
--resource-arn "arn:aws:bedrock-agentcore:REGION:ACCOUNT:payment-manager/MANAGER-ID" \
--log-type "TRACES"
aws logs put-delivery-destination \
--name "payments-xray" \
--delivery-destination-type "XRAY"
aws logs create-delivery \
--delivery-source-name "payments-traces-source" \
--delivery-destination-arn "arn:aws:logs:REGION:ACCOUNT:delivery-destination:payments-xray"
Configuring only the log pipeline — the natural first step, since it's what the getting-started docs walk you through — leaves X-Ray permanently empty with no error anywhere to point at the missing piece. I discovered this in July and confirmed it was not a platform gap in August: once the trace pipeline is wired up, Bedrock.AgentCore.Payments.ProcessPayment spans appear within a couple of minutes carrying the documented attributes: payments.spend_amount, payments.merchant, payments.payment_session_id, payments.payment_instrument_id, down to aws.request_id.
That last set of attributes is the actual payoff. Metrics tell you that spend happened; a span tells you which spend, tied to which session, which merchant, which request. For a platform where agents pay third parties on a customer's behalf, that's the mechanism for answering "which specific payment failed, for which customer, at which merchant, for how much" during an incident or a billing dispute — one X-Ray trace correlating the Gateway request, the Cedar decision, the Lambda execution, the ProcessPayment call, and the merchant settlement, without grepping across log groups by hand.
Agent-Native Payment Flow
The script path is useful for proof. The agent-native path is what product builders will care about.
I tested the Strands AgentCorePaymentsPlugin with the real http_request tool. The tool returned HTTP 402. The plugin detected it, called AgentCore Payments through PaymentManager.generate_payment_header, injected a PAYMENT-SIGNATURE header, and requested a retry.
The tool retried with the injected header and got back HTTP 200 — the plugin owns the entire handshake, proof generation through settled resource access, without the tool author writing any custom 402 handling.
Two Prerequisites That Will Block You Cold
Using Coinbase as a payment provider requires subscribing to "Coinbase Wallets for AgentCore Payments" in AWS Marketplace. Until that subscription is active, CreatePaymentConnector and every wallet operation reject with SubscriptionRequiredException. It's a real, metered charge — $0.005 per wallet operation, consolidated onto your AWS bill.
Separately, Coinbase CDP wallet permissions are wallet-scoped, time-bound grants: the end user picks 7, 30, 60, or 90 days when authorizing the agent to sign. ProcessPayment returns AccessDeniedException: Delegated signing grant is not active the moment it lapses. If a payment call starts failing with that specific error, check the grant's expiry before assuming anything else is wrong — it fails the exact same way whether the grant expired yesterday or was never issued.
Quick Create's Seatbelt
Quick Create provisions Coinbase credentials for you: authorize once via OAuth in the console, and AgentCore creates the payment credential provider on your behalf, no pasted API keys. Point it at a Coinbase project that already has a manually-created Wallet Secret, though, and it refuses outright: "This Coinbase project already has a Wallet Secret, so a new one can't be created automatically."
That's a safe-failure guardrail, not a bug — silently generating a second secret could orphan wallets already derived from the first. But Quick Create only completes its automatic path for a project that has never had a Wallet Secret generated, and the quick-start guides still document manual CDP key generation first. Follow the docs in order and you permanently lose access to Quick Create for that project; only a fresh Coinbase project gets the one-click path.
Three Bugs That Weren't AWS's
Moving the paid-fetch implementation behind Gateway as a Lambda target kept failing the same way at first: a proof generated, a retry rejected with an empty 402 body. The obvious suspects were a rate limit on the sandbox endpoint or an edge-cache hit. Both testable, both wrong — a plain script from a local machine worked fine against the identical endpoint.
Temporary CloudWatch logging inside the Lambda found the real story: ProcessPayment was returning a fully valid, signed proof, authorization and signature intact, on every call. The Lambda was throwing it away:
# what the Lambda was doing — wrong
payment = client.process_payment(...)
result = payment.get("processPayment", {}) # this key does not exist
proof = result.get("paymentOutput", {}) # -> always {}
# the boto3 response is flat, no wrapper
# {"processPaymentId": "...", "status": "PROOF_GENERATED",
# "paymentOutput": {"cryptoX402": {"payload": {...signed...}}}}
proof = payment.get("paymentOutput", {}) # correct
The process_payment reference confirms it: processPaymentId, status, and paymentOutput are flat, top-level response fields, matching every AWS SDK example published for the API. The bad .get() call silently returned {}, so the retry always carried payload: null — which any x402-compliant merchant correctly rejects.
Two smaller bugs rode along with it: the deployed Lambda source was missing import os, crashing on every cold start behind a retry loop that swallowed the real traceback; and a policy-engine ID was hardcoded from an earlier public-sanitization pass instead of reading from the environment. None of the three were platform bugs. All three had been quietly filed under "the sandbox is flaky" until logging showed otherwise. "It's the network," "it's rate limiting," and "it's flaky" are all real phenomena — in a system with this many moving parts, any of them was plausible. None of them were the fix. Reading the actual response was.
What Is Still Missing
This experiment ran on testnet and does not prove mainnet charging, customer billing, tax handling, refund workflows, fraud handling, or production compliance. Those are different systems.
The core primitive (payment proof generation, session budgets, policy gating, observability) has been validated against GA and all endpoint retry tests have succeeded end-to-end with settled HTTP 200 responses. CloudWatch traces and X-Ray span correlation were reconfirmed as long as both delivery pipelines are wired — that was a configuration gap, not a platform gap.
Coinbase's x402 Bazaar — a curated MCP server exposing thousands of pay-per-use x402 endpoints — is reachable through Gateway. The open thread: I still haven't proven end-to-end agent discovery of a live Bazaar endpoint with a successful settled payment, only that the pieces wire. The blocks are technical (Gateway-to-Bazaar MCP compatibility) not architectural. The pattern itself is sound.
So What
The interesting part of agent payments is not that an agent can call a payment API.
The interesting part is that spending can be bounded by infrastructure, authorized by policy, observed through logs and metrics, and executed without putting wallet secrets into model context.
That is the pattern worth carrying forward:
Intent lives in the model.
Authority lives in policy.
Budget lives in the payment session.
Credentials live outside the model.
Evidence lives in telemetry.
An agent that can pay for its tools is not a wallet with a chat box. It is a governed actor with a narrow financial action path. That is the difference between a demo and something you can reason about.
Top comments (0)