---
title: "An Agent That Can Pay for Its Tools"
description: "Payment is not a tool call—it is a governed financial action. An agent that pays requires user consent, credential isolation, session budgets, policy checks, and a proof path the model cannot bypass."
canonical_url: "https://artificialcuriositylabs.ai/posts/payments-governed-financial-action/"
md_url: "https://artificialcuriositylabs.ai/posts/payments-governed-financial-action.md"
published_at: "2026-07-15T07:00:00.000Z"
tags:
  - "agents"
  - "agentcore"
  - "bedrock"
  - "payments"
---

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](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/payments.html), [Coinbase CDP](https://docs.cdp.coinbase.com/wallets/using-wallets/delegated-signing), [x402](https://docs.cdp.coinbase.com/x402/quickstart-for-buyers), 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:

```mermaid
flowchart LR
    subgraph MODEL["Model context"]
        INTENT["intent: pay for this resource"]
    end
    subgraph OUTSIDE["Outside model context"]
        CEDAR["Cedar: allow / deny"]
        BUDGET["Payment session: budget check"]
        CDP["Coinbase CDP: wallet secret, signing"]
        PROOF["AgentCore: proof generation"]
    end
    INTENT --> CEDAR --> BUDGET --> CDP --> PROOF
```

The model only ever produces the box on the left. Every box on the right — the wallet secret, the spend limit, the policy decision, the signed proof — is infrastructure the model never touches and can't be talked out of.

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.

## How The Flow Actually Ran

This is the shape of the runs described below, not a generic template — the values are the ones the tests actually produced:

```mermaid
sequenceDiagram
    participant Agent
    participant GW as Gateway/Cedar
    participant PM as Payment Manager
    participant CDP as Coinbase CDP
    participant Res as Paid Resource

    Agent->>Res: GET /resource (Base Sepolia testnet)
    Res-->>Agent: 402 + x402 v2 requirement, payTo address
    Agent->>GW: invoke governed action, amount=45.0
    GW-->>Agent: allow
    Note over GW: amount=600.0 instead -> deny, JSON-RPC -32002, no payment attempted
    Agent->>PM: ProcessPayment(challenge)
    PM->>PM: check session budget
    Note over PM: 2nd request after budget exhausted -> InsufficientBudget, stop here
    PM->>CDP: sign with delegated wallet
    CDP-->>PM: signed proof
    PM-->>Agent: PAYMENT-SIGNATURE proof
    Agent->>Res: retry + proof
    Res-->>Agent: 200 OK, merchant wallet credited
```

## 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](https://docs.aws.amazon.com/bedrock-agentcore/latest/APIReference/API_ProcessPayment.html). 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:

```text
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:

```bash
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](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/payments-observability.html): `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.

One gotcha worth knowing before you write this yourself: `process_payment`'s `boto3` response is flat, not wrapped. It's tempting to read `payment.get("processPayment", {}).get("paymentOutput", {})`, because that mirrors the API's name — but there is no `processPayment` key. `processPaymentId`, `status`, and `paymentOutput` are all top-level fields, [as the API reference shows](https://docs.aws.amazon.com/botocore/latest/reference/services/bedrock-agentcore/client/process_payment.html). Get that wrong and the call still returns a fully valid, signed proof — your code just silently reads an empty dict off it, sends a null payload on retry, and any x402-compliant merchant correctly rejects it. The proof was never the problem; the unwrap was.

## Two Prerequisites That Will Block You Cold

Using Coinbase as a payment provider requires subscribing to "[Coinbase Wallets for AgentCore Payments](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/payments-marketplace-subscription.html)" 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](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/payments-fund-wallet.html) 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](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/release-notes.html) 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.

## 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](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/payments.html) — 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:

```text
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.
