AWS Builder Center
Discover Transactional Agents with Amazon Bedrock AgentCore Payments

Discover Transactional Agents with Amazon Bedrock AgentCore Payments

When we build AI agents that consume third-party APIs, the main challenge is often not the model's reasoning capabilities it's payments.

Technical Lead
An agent may discover that an endpoint costs 0.0005 USDC, but someone still needs to sign that transaction. Traditionally, that means placing a private key somewhere inside the agent's code.
That is exactly what we want to avoid.
This is where the x402 protocol comes in. It reuses an HTTP status code that almost nobody used before — 402 Payment Required — allowing a server to respond with something equivalent to:
"This costs X. Pay for it and try again."
Amazon Bedrock AgentCore Payments handles the other side of the transaction. It manages the wallet and signs transactions server-side, which means the agent never sees a private key and can only spend within the budget we assign to it.
In this article, we will deploy a complete end-to-end flow:
  • A seller that charges for content behind CloudFront.
  • An AI agent that detects an HTTP 402 response and pays for the requested resource.
  • A web interface where we can see the entire process in action.
Everything will run on AWS.

Requirements

  • Node.js 24
  • Python 3.11 or later
  • AWS CLI 2.x, with aws sts get-caller-identity working correctly
  • AWS CDK 2.x
  • Docker running — not just installed. The agent is deployed as a container image that we build locally and push to ECR.

AWS account

For this example, we will use the us-east-1 region.
Your AWS credentials should have sufficient permissions for services such as:
  • Amazon CloudFront
  • Amazon CloudWatch
  • Amazon S3
  • Amazon Bedrock AgentCore
  • AWS Lambda
You also need access to Bedrock models enabled in your account.
It is a good idea to verify this before deploying anything:
1
2
3
4
aws bedrock-runtime converse \
--model-id us.anthropic.claude-sonnet-4-5-20250929-v1:0 \
--messages '[{"role":"user","content":[{"text":"hello"}]}]' \
--inference-config '{"maxTokens":10}'
If this returns an AccessDeniedException mentioning aws-marketplace:Subscribe, model access has not been enabled for the account.
You can fix this from the AWS console under:
Bedrock → Model access
You also need to bootstrap CDK once per AWS account and region:
1
npx cdk bootstrap aws://<ACCOUNT_ID>/us-east-1

Coinbase Developer Platform

AgentCore Payments signs transactions through a Coinbase CDP project, so we need to configure one before getting started.
You will need:
  • An account at portal.cdp.coinbase.com
  • An API key:
    • CDP_API_KEY_ID
    • CDP_API_KEY_SECRET
  • A wallet secret:
    • CDP_WALLET_SECRET
  • Delegated signing enabled in your project policies.
Delegated signing is probably the easiest requirement to overlook.
Without it, every ProcessPayment request will fail with:
1
Delegated signing is not enabled for your Coinbase project
You will also need access to an email address.
When the payment instrument is created, CDP sends a wallet activation link to that email address. Until the activation link is opened, the wallet cannot sign transactions.

Test funds

We will use the Circle faucet at faucet.circle.com.
This allows us to fund our wallet on the testnet.

Proposed Architecture

There are two CloudFront distributions.
One exposes our frontend, while the other exposes the backend API protected by the payment paywall that the agent consumes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Browser
|
v
CloudFront + S3 (web interface)
|
v
API Gateway (hard 29-second limit)
|
v
Lambda (proxy: InvokeAgentRuntime)
|
v
AgentCore Runtime (container running the Strands agent)
|
+--> AgentCore Payments --> Coinbase CDP (wallet, signing)
|
+--> CloudFront + Lambda@Edge (seller: verifies x402, returns 402 or content)
|
v
S3 (paid content)
The important part of this design is understanding which component knows what.
  • The browser does not know the seller's URL. It asks the agent for a path such as /api/weather-data, and the agent resolves that path against its own SELLER_API_URL. This prevents the payment origin from being manipulated by the client.
  • The agent does not have access to private keys. It calls ProcessPayment and receives a payment proof.
  • The payment session defines the available budget. The agent operates under an IAM role called ProcessPaymentRole, which can only execute payments within that limit. It cannot create payment sessions or modify the budget.
The flow consists of three steps, and each one is a separate call to the agent:
  1. request_content("/api/weather-data") returns a 402 response containing the x402_payload.
  2. process_payment(x402_payload) returns the PROOF_GENERATED status.
  3. request_content_with_payment("/api/weather-data") retries the request using the PAYMENT-SIGNATURE header and receives a 200 response containing the requested content.
We separate these actions for a practical reason:
API Gateway terminates requests after 29 seconds.
If we ask the agent to perform all three steps within a single prompt, the HTTP response may time out even though the agent eventually completes the operation.
The seller's content comes from two different sources.
Three endpoints return data embedded directly inside the Lambda@Edge function, while another three retrieve their content from S3:
  • /api/weather-data — 0.0005 USDC, embedded
  • /api/premium-article — 0.001 USDC, embedded
  • /api/market-analysis — 0.002 USDC, embedded
  • /api/tutorial — 0.003 USDC, S3
  • /api/research-report — 0.005 USDC, S3
  • /api/dataset — 0.01 USDC, S3

Walkthrough

Step 1: Deploy the Seller

First, we create:
  • The CloudFront distribution
  • The x402 verifier running as Lambda@Edge
  • The S3 bucket containing the paid content
1
2
cd seller-infrastructure
cp .env.example .env
Inside the .env file, configure:
1
PAYMENT_RECIPIENT_ADDRESS
This is the wallet that will receive the payments.
The stack intentionally fails if this variable is not defined.
Deploying a payment paywall without explicitly configuring the recipient could result in payments being sent to the wrong destination.
Now deploy the stack:
1
2
npm install
npx cdk deploy
Deploying CloudFront together with Lambda@Edge is relatively slow.
A deployment time of 10 to 15 minutes is normal.
From the CloudFormation output, we are interested in:
1
DistributionUrl
This is the seller's URL.
Next, upload the content for the three endpoints backed by S3:
1
2
./scripts/upload-content.sh $(aws cloudformation describe-stacks --stack-name X402SellerStack \
--query "Stacks[0].Outputs[?OutputKey=='ContentBucketName'].OutputValue" --output text)
Now verify that the paywall is working:
1
curl -s -o /dev/null -w '%{http_code}\n' https://<seller>.cloudfront.net/api/weather-data
The response should be:
1
402
If it returns 200, you are probably pointing to the wrong CloudFront distribution.

Step 2: Deploy the Payer IAM Roles

The payment resources we create in Step 3 require the ARNs of the IAM roles created by this stack.
1
2
3
cd ../payer-infrastructure
npm install
npx cdk deploy X402PayerAgentStack
We need to specify the stack explicitly because the CDK application defines two stacks.
Running:
1
cdk deploy
by itself will fail and ask which stack you want to deploy.
The second stack is:
1
X402ObservabilityStack
It creates CloudWatch dashboards and alarms and is optional.
From the stack output, save the following values:
1
2
3
ProcessPaymentRoleArn
ResourceRetrievalRoleArn
AgentRuntimeRoleArn

Step 3: Create the AgentCore Payments Resources

A single script creates:
  • The credential provider
  • The payment manager
  • The connector
  • The payment instrument — our wallet
  • The payment session with its associated budget
Run:
1
2
3
4
5
6
7
8
9
10
11
12
cd ../payer-agent
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"

export CDP_API_KEY_ID=...
export CDP_API_KEY_SECRET=...
export CDP_WALLET_SECRET=...
export RESOURCE_RETRIEVAL_ROLE_ARN=<ResourceRetrievalRoleArn from step 2>
export USER_EMAIL=you@example.com
export USER_ID=x402-demo-user

python scripts/setup_payments.py
The script prints a wallet activation URL.
You must open that URL and complete the activation process. Otherwise, the wallet will not be able to sign transactions.
The script then prints the values that we need to add to the .env file in the next step.
Before continuing, fund the wallet with Base Sepolia USDC using the Circle faucet.
The wallet address appears in the script output.

Step 4: Configure and Deploy the Agent

Create the environment file:
1
cp .env.example .env
At minimum, configure:
1
2
3
4
5
6
7
8
AWS_REGION=us-east-1
BEDROCK_MODEL_ID=us.anthropic.claude-sonnet-4-5-20250929-v1:0
SELLER_API_URL=https://<seller>.cloudfront.net
MANAGER_ARN=<from step 3>
PAYMENT_SESSION_ID=<from step 3>
PAYMENT_INSTRUMENT_ID=<from step 3>
PROCESS_PAYMENT_ROLE_ARN=<ProcessPaymentRoleArn from step 2>
USER_ID=x402-demo-user
SELLER_API_URL should not include a trailing slash.
The agent's content tools receive a path and append it to this base URL.
Leave:
1
AGENT_RUNTIME_ARN
empty.
The deployment script writes the runtime ARN back into the file after the deployment completes.
Now deploy the agent:
1
python scripts/deploy_to_agentcore.py
This script:
  1. Builds the container image.
  2. Pushes it to Amazon ECR.
  3. Creates or updates the AgentCore Runtime.
An important detail here is that only environment variables included in the deployment script's allowlist are passed to the runtime.
In practice, this means that if you add a new variable to .env but do not also add it to that allowlist, the agent will never see it.

Step 5: Deploy the Web Interface

Move to the web infrastructure:
1
2
3
cd ../web-ui-infrastructure
npm install
cd ../web-ui && npm install && cd ../web-ui-infrastructure
Then deploy:
1
2
3
WALLET_ADDRESS=<wallet from step 3> \
AGENT_RUNTIME_ARN=<from the .env file in step 4> \
./scripts/deploy.sh
The deployment script:
  1. Deploys the infrastructure stack.
  2. Retrieves the API Gateway URL.
  3. Embeds that URL into the frontend build.
  4. Uploads the frontend to S3.
Both environment variables are important.
AGENT_RUNTIME_ARN identifies the AgentCore Runtime invoked by the Lambda function.
WALLET_ADDRESS is displayed in the wallet panel of the frontend.
Open the URL returned as:
1
WebUiUrl
Now we can test the entire flow.
Choose one of the available pieces of content.
Clicking Request Content should return the 402 Payment Required response along with the payment details.
Then clicking Confirm Payment should execute the payment and return the protected content.
You can also test the agent directly through the API:
1
2
3
curl -s -X POST https://<api-id>.execute-api.us-east-1.amazonaws.com/prod/invoke \
-H 'Content-Type: application/json' \
-d '{"message":"Use the request_content tool to fetch content from /api/weather-data"}'

Example

First, we add funds to our wallet.
From the frontend, we can verify the wallet address and available balance.
Next, we select the premium content that we want the agent to retrieve.
Finally, we proceed with the payment.

Common Errors and How to Debug Them

These are some of the issues that took real time to troubleshoot, along with the error message and underlying cause.

Received error (422) from runtime

CloudWatch may show nothing other than the 422.
This usually means that invoke_agent_runtime was called without specifying contentType.
That parameter is optional in boto3, so the request reaches the container without a JSON content type.
FastAPI then rejects the request body before our handler is executed.
That is why there are no application logs: the framework itself generates the 422.
The fix is to explicitly provide:
1
contentType='application/json'

Invocation of model ID ... with on-demand throughput isn't supported

In this case, BEDROCK_MODEL_ID is pointing directly to a foundation model ID.
You need to use a cross-region inference profile instead — in this case, the same model ID with the us. prefix.
You can check which inference profiles are currently available:
1
2
aws bedrock list-inference-profiles \
--query "inferenceProfileSummaries[?contains(inferenceProfileId,'sonnet')].inferenceProfileId"

AccessDeniedException ... not authorized to perform: bedrock-agentcore:InvokeAgentRuntime on resource: .../runtime/<id>/runtime-endpoint/DEFAULT

The IAM policy may grant access to the runtime ARN itself, but authorization occurs against the runtime-endpoint subresource.
You need to allow both:
1
<runtime-arn>
and:
1
<runtime-arn>/*

Missing or invalid paymentManagerArn / paymentSessionId / paymentInstrumentId

The values may exist in your local .env file but never have reached the actual runtime environment.
Check the runtime using get-agent-runtime and inspect the contents of:
1
environmentVariables

A 403 response containing HTML instead of JSON when retrying with the payment proof

The retry was probably made using POST.
The /api/* CloudFront behavior only allows:
1
2
3
HEAD
GET
OPTIONS
CloudFront therefore rejects the HTTP method before the x402 verifier is ever executed.
The clue is the response format.
If the error is HTML instead of JSON, it was probably generated by CloudFront rather than by our Lambda function.

The .env file appears to be ignored

load_dotenv() does not overwrite variables that already exist in the process environment.
An old export command in your terminal silently takes precedence over the .env value.
An even more confusing situation occurs when an environment variable contains the literal value:
1
None
In Python, that is still a non-empty string and therefore evaluates as truthy, making the variable appear to be configured.
Check your environment with:
1
env | grep AGENT_

Endpoint request timed out from the browser

This is caused by API Gateway's 29-second timeout limit.
The agent may still be working successfully in the background.
What was lost is the HTTP response.
This is why the frontend performs one request per step instead of trying to execute the entire payment flow in one API call.

Inspecting the Agent Runtime Logs

To see what is actually happening inside the agent:
1
2
aws logs tail /aws/bedrock-agentcore/runtimes/<runtime-id>-DEFAULT --since 10m --format short \
| grep -v "GET /ping"
Filtering out /ping is not merely cosmetic.
AgentCore performs a container health check roughly twice per second, and those log lines can quickly bury everything else.

Recommendations

  • Treat the payment session as what it really is: an expiring budget.
    It is created using maxSpendAmount and expiryTimeInMinutes.
    Once it expires, you need to create a new session and update PAYMENT_SESSION_ID.
    It is also worth checking availableLimits before assuming that a payment failed for some unrelated reason.
  • Do not allow the frontend to construct payment URLs.
    Let the frontend submit paths and have the agent resolve those paths against its own configuration.
    If the agent accepts arbitrary absolute URLs, someone could redirect the payment flow toward another origin.
  • Keep the agent's IAM role limited to ProcessPayment.
    If a backend needs to query balances or inspect payment instruments, use separate credentials rather than expanding the agent's payment role.
  • Update the AgentCore Runtime instead of recreating it.
    If a deployment deletes and recreates the runtime, its ARN changes.
    Everything referencing the previous ARN — such as the frontend Lambda or the .env file — will continue pointing to a resource that no longer exists.
  • Always start on a testnet with small budgets.
    An agent that successfully executes a payment but later fails to retrieve the protected content has still spent the money.
The complete project code is available in the repository, together with a QUICKSTART.md that covers both deployment and cleanup of resources that CDK does not manage directly, including:
  • The AgentCore Runtime
  • The ECR container image
  • The AgentCore Payments resources
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article