Amazon Bedrock AgentCore + Privy
Enable AI agents to make autonomous stablecoin payments via AWS Bedrock AgentCore Payments using Privy embedded wallets.
This recipe covers how to use Privy embedded wallets as the payment provider for AWS Bedrock AgentCore Payments, so agents can autonomously pay for APIs, MCP servers, and content using the x402 protocol.
Overview
Amazon Bedrock AgentCore is AWS’s managed platform for building and operating AI agents. AgentCore Payments is the service that lets an agent pay for paid endpoints (APIs, MCPs, and web content) over the x402 protocol. It owns the entire payment lifecycle: storing provider credentials, enforcing per-session spend limits, signing the payment, and recording the transaction.
Privy provides the user-owned embedded wallet that holds the stablecoins. AgentCore connects to Privy through a PaymentConnector, retrieves signing material through AgentCore Identity, and calls ProcessPayment to produce the signed x402 proof. The agent never talks to Privy’s wallet APIs directly — it calls AgentCore, and AgentCore talks to Privy.
With this integration, agents can:
- Discover and call paid APIs, MCP tools, and paywalled content that return
402 Payment Required
- Accept funds from the end user via fiat (cards, Apple Pay, Google Pay, ACH) or USDC stablecoin
- Pay autonomously to the paid endpoint over x402 (v1 and v2)
- Operate within spend limits enforced per Payment Session by AgentCore
- Settle on-chain in USDC
How it works
AgentCore Payments is built from a small set of resources. Create them once, in order, and the agent uses them at runtime.
- PaymentManager — The top-level resource for your account. It defines how agents authenticate (
AWS_IAM or CUSTOM_JWT) and references the IAM execution role AgentCore assumes to do payment work.
- PaymentCredentialProvider — Stores your Privy credentials (App ID, App Secret, authorization key) inside AgentCore Identity, backed by AWS Secrets Manager. The agent runtime never reads these directly.
- PaymentConnector — Binds the PaymentManager to a payment provider. For Privy, create a
StripePrivy connector that references the credential provider above.
- PaymentInstrument — The end user’s wallet. Create it through the AgentCore
CreatePaymentInstrument API (type EMBEDDED_CRYPTO_WALLET), not through the Privy SDK. AgentCore provisions the Privy embedded wallet on your behalf and returns the wallet address.
- PaymentSession — A time-bounded spending context (
maxSpendAmount, currency, expiryTimeInMinutes). When the session expires or the limit is reached, further payments in that session are denied.
- ProcessPayment — At runtime, when the agent hits a
402, it calls ProcessPayment. AgentCore checks the session limit, retrieves the Privy signing key from Identity, signs the x402 proof, and returns it. The agent retries the request with the proof.
The runtime flow:
1. Agent calls a paid resource (x402) -> 402 Payment Required
2. Agent calls AgentCore ProcessPayment -> session limit checked
3. AgentCore retrieves the Privy signing key -> signs the x402 proof
4. Agent retries the request with the proof -> 200 OK + paid content
Your agent --(402)--> Paid resource
Your agent --(ProcessPayment)--> AgentCore Payments --(via Identity)--> Privy wallet
Your agent --(retry with signed proof)--> Paid resource
Prerequisites
- AWS account with AgentCore Payments access — Install and configure the AWS CLI v2 and Python 3.10+ with
boto3. Verify your credentials with aws sts get-caller-identity. AgentCore Payments is available in us-east-1, us-west-2, eu-central-1, and ap-southeast-2.
- A dedicated Privy app — Create a developer account at dashboard.privy.io and create a dedicated Privy app for AgentCore. Do not reuse an app that serves other purposes; this keeps credential scope and auditing clean. Copy the App ID and App Secret from the app settings.
- A Privy authorization key — In your Privy app, go to Wallet Infrastructure > Authorization > New Key to generate a P-256 key pair. This key is what AgentCore uses to sign wallet operations. Note the Authorization ID (signer ID) shown alongside the key.
Privy prefixes the generated private key with wallet-auth:. AgentCore Payments does not accept this prefix. Strip it and store only the raw base64 content after the prefix.Privy gives you: wallet-auth:MBMGByqGSM49AgEGCC...
Store only: MBMGByqGSM49AgEGCC...
After this, there are four Privy values to hand to AgentCore: App ID, App Secret, Authorization ID, and the Authorization Private Key (prefix stripped). For full provider detail, see the AWS Prerequisites for AgentCore payments.
Step 1: Store Privy credentials in AgentCore Identity
Create a PaymentCredentialProvider so AgentCore can store your Privy credentials securely. Resource names must be lowercase alphanumeric with hyphens only.
import boto3
cp = boto3.client("bedrock-agentcore-control", region_name="us-west-2")
cred = cp.create_payment_credential_provider(
name="agentcore-privy-creds",
credentialProviderVendor="StripePrivy",
providerConfigurationInput={
"stripePrivyConfiguration": {
"appId": PRIVY_APP_ID,
"appSecret": PRIVY_APP_SECRET,
"authorizationId": PRIVY_AUTH_ID,
"authorizationPrivateKey": PRIVY_AUTH_PRIVATE_KEY, # wallet-auth: prefix stripped
}
},
)
credential_provider_arn = cred["credentialProviderArn"]
Never embed Privy credentials in agent source code or paste them in chat. Load them from
environment variables (source .env.payments) at setup time. Once stored in AgentCore Identity,
restrict the underlying Secrets Manager secret to the AgentCore Payments service role only.
Step 2: Create the Payment Manager
The Payment Manager needs an IAM role that trusts bedrock-agentcore.amazonaws.com and grants GetWorkloadAccessToken, GetResourcePaymentToken, and secretsmanager:GetSecretValue (see the IAM roles page for the exact policy).
import uuid, time
mgr = cp.create_payment_manager(
name="agentcoreprivy",
description="Payment manager for Privy wallets",
authorizerType="AWS_IAM",
roleArn=role_arn,
clientToken=str(uuid.uuid4()),
)
payment_manager_id = mgr["paymentManagerId"] # used for control-plane ops
payment_manager_arn = mgr["paymentManagerArn"] # used for data-plane ops
# Wait for READY before creating a connector
while cp.get_payment_manager(paymentManagerId=payment_manager_id)["status"] != "READY":
time.sleep(5)
Step 3: Create the Payment Connector
Bind the manager to Privy by referencing the credential provider from Step 1.
conn = cp.create_payment_connector(
paymentManagerId=payment_manager_id,
name="agentcoreprivyconnector",
description="Privy connector",
type="StripePrivy",
credentialProviderConfigurations=[
{"stripePrivy": {"credentialProviderArn": credential_provider_arn}}
],
clientToken=str(uuid.uuid4()),
)
connector_id = conn["paymentConnectorId"]
Step 4: Create the Payment Instrument (wallet)
Create the user’s embedded wallet through AgentCore. The end user’s email is linked here: it is the account they will log into when granting the agent permission to spend.
dp = boto3.client("bedrock-agentcore", region_name="us-west-2")
instr = dp.create_payment_instrument(
paymentManagerArn=payment_manager_arn,
paymentConnectorId=connector_id,
userId="agentcore-user",
paymentInstrumentType="EMBEDDED_CRYPTO_WALLET",
paymentInstrumentDetails={
"embeddedCryptoWallet": {
"network": "ETHEREUM", # ETHEREUM covers Base + Base Sepolia
"linkedAccounts": [{"email": {"emailAddress": END_USER_EMAIL}}],
}
},
clientToken=str(uuid.uuid4()),
)
instrument = instr.get("paymentInstrument", instr)
payment_instrument_id = instrument["paymentInstrumentId"]
wallet_address = instrument["paymentInstrumentDetails"]["embeddedCryptoWallet"]["walletAddress"]
A new instrument starts with 0 USDC and the agent has no permission to spend until the end user grants it. Funding and delegation come next, in that order.
Step 5: Grant the agent permission (delegation)
This is a required step. The agent’s authorization key must be added as a signer on the end user’s embedded wallet, and only the user can approve that.
- Stand up a frontend using the Privy AgentCore SDK, which provides a reference wallet hub for login, agent connection, and on-ramping.
- Have the end user log in with the email linked in Step 4 (
END_USER_EMAIL).
- The user approves delegation for the wallet, authorizing the agent to sign within AgentCore’s controls.
If delegation is skipped, ProcessPayment fails with a “Delegation not completed” error. The
agent acts as an authorized signer only: the user retains ownership and can revoke access at any
time.
Step 6: Fund the wallet
Once delegation is approved, fund the wallet with USDC.
- Testnet: Get free testnet USDC on Base Sepolia from Circle’s faucet and send it to the wallet address from Step 4.
- Mainnet: The end user funds the wallet through the Privy wallet hub: crypto-to-crypto transfer or supported fiat methods (cards, Apple Pay, Google Pay, ACH; availability varies by region).
Step 7: Create a Payment Session and enable payments
Create a session to bound spending, then wire payment handling into your agent.
session = dp.create_payment_session(
paymentManagerArn=payment_manager_arn,
userId="agentcore-user",
expiryTimeInMinutes=60,
limits={"maxSpendAmount": {"value": "5.00", "currency": "USD"}},
)
payment_session_id = session["paymentSession"]["paymentSessionId"]
The agent uses an x402-aware fetch tool. When it hits a 402, the tool reads the challenge, calls ProcessPayment, and retries with the signed proof. AgentCore checks the session limit, signs through Privy, and returns the proof.
import os, json, base64, httpx, boto3
dp = boto3.client("bedrock-agentcore", region_name=os.environ["AWS_REGION"])
def x402_fetch(url: str, method: str = "GET") -> str:
"""Fetch a URL, paying via AgentCore + Privy if it returns 402."""
resp = httpx.request(method, url, timeout=30)
if resp.status_code != 402:
return json.dumps({"status_code": resp.status_code, "body": resp.text})
# Extract the x402 challenge (body for v1, payment-required header otherwise)
challenge = None
try:
body = resp.json()
if "x402Version" in body and "accepts" in body:
challenge = body
except Exception:
pass
if not challenge and (h := resp.headers.get("payment-required")):
challenge = json.loads(base64.b64decode(h))
if not challenge:
return json.dumps({"error": "402 without an x402 challenge"})
# Pick the accept that matches your wallet's network family.
# This recipe uses an ETHEREUM (EVM) wallet, so prefer eip155/base; fall back to the first.
accepts_list = challenge["accepts"]
accepts = next(
(a for a in accepts_list
if str(a.get("network", "")).lower().startswith(("eip155", "base", "ethereum"))),
accepts_list[0],
)
# AgentCore signs the payment through the Privy wallet.
# Forward the FULL accept object as the payload — it includes `extra`
# (e.g. {"name": "USDC"}), which is required for EVM payments.
pay = dp.process_payment(
paymentManagerArn=os.environ["PAYMENT_MANAGER_ARN"],
paymentInstrumentId=os.environ["PAYMENT_INSTRUMENT_ID"],
paymentSessionId=os.environ["PAYMENT_SESSION_ID"],
userId=os.environ["PAYMENT_USER_ID"],
paymentType="CRYPTO_X402",
paymentInput={"cryptoX402": {
"version": str(challenge.get("x402Version", "1")),
"payload": accepts,
}},
)
out = pay["paymentOutput"]["cryptoX402"]
version = int(challenge.get("x402Version", 1))
# Build the retry proof to match the challenge version.
if version >= 2:
proof = {
"x402Version": 2,
"resource": challenge.get("resource"),
"accepted": accepts,
"extensions": challenge.get("extensions", {}),
"payload": out["payload"],
}
header = "PAYMENT-SIGNATURE"
else:
proof = {
"x402Version": 1,
"scheme": accepts.get("scheme", "exact"),
"network": accepts["network"],
"payload": out["payload"],
}
header = "X-PAYMENT"
token = base64.b64encode(json.dumps(proof, separators=(",", ":")).encode()).decode()
# Retry with a FRESH client — reusing cookies from the 402 can break the retry
with httpx.Client(verify=True) as client:
retry = client.request(method, url, headers={header: token}, timeout=30)
return json.dumps({
"status_code": retry.status_code,
"body": retry.text,
"payment_made": 200 <= retry.status_code < 300,
})
Register x402_fetch as a tool in your agent framework (Strands, LangGraph, OpenAI Agents SDK, etc.) and the agent will pay for 402 resources autonomously.
Use a fresh HTTP client for the retry. Some merchants set cookies on the 402 response that
cause the paid retry to fail if reused. Also build the proof to match the challenge’s
x402Version: a v2 merchant silently ignores a v1 X-PAYMENT header and re-issues the same 402.
Spend controls
AgentCore enforces spending at the Payment Session level. There are no per-recipient allowlists or separate budget objects.
| Field | Description |
|---|
maxSpendAmount | The ceiling for the session |
currency | The session currency |
expiryTimeInMinutes | Keep sessions short-lived (60 minutes or less); create a fresh session per user interaction rather than reusing a long-lived one |
When a payment would exceed the limit or the session has expired, AgentCore denies it before signing.
Observability
AgentCore Payments integrates with Amazon CloudWatch. Once enabled, every data plane API call (ProcessPayment, CreatePaymentInstrument, etc.) emits metrics, logs, and X-Ray spans automatically.
Enable it
- Create a CloudWatch log group (e.g.
/bedrock-agentcore/payments/my-logs).
- Grant your IAM principal vended-log and X-Ray delivery permissions (
logs:CreateDelivery, xray:PutTraceSegments, bedrock-agentcore:AllowVendedLogDeliveryForResource, and related).
- On the Payment Manager details page, under Log deliveries and tracing, point log delivery at your log group and enable traces.
Vended metrics
Key payment metrics published to CloudWatch: PaymentRequestCount, PaymentSuccessCount, PaymentFailureCount, PaymentLatency, and SpendAmount, plus per-operation OperationSuccess, OperationFailure, OperationLatency, Throttles, UserErrors, and ActiveSessions. Dimensions: Operation, PaymentManagerId, PaymentConnectorId, AgentName, and Currency.
Alarm on PaymentFailureCount (misconfiguration/abuse signal) and PaymentLatency against your SLA.
Vended spans
One span per API call, named Bedrock.AgentCore.Payments.<Operation>, viewable in X-Ray with payment-specific attributes: spend_amount, spend_currency, merchant (payTo), session_remaining_budget, total_budget_amount, and token_fetch_latency_ms — plus resource IDs and standard AWS attributes.
For the full reference, see the AgentCore Payments observability docs.
Supported networks
AgentCore Payments with Privy settles in USDC. For instrument creation, choose a network family; the x402 challenge then specifies the exact chain.
| Network (instrument) | Chains | Asset | Type |
|---|
ETHEREUM | Base Sepolia (base-sepolia / eip155:84532) | USDC | Testnet |
ETHEREUM | Base (eip155:8453), Ethereum (eip155:1) | USDC | Mainnet |
SOLANA | Solana Devnet (solana-devnet) | USDC | Testnet |
SOLANA | Solana Mainnet | USDC | Mainnet |
For development, start with ETHEREUM / Base Sepolia and free testnet USDC from Circle’s faucet.
Testing
- Use Base Sepolia for development.
- Fund the wallet with testnet USDC from Circle’s faucet.
- Point the agent at an x402-enabled test endpoint and confirm it pays and returns content. Browse live x402 services at x402scan.com.
If the agent loops on 402 after a successful ProcessPayment, the most common causes are cookie contamination on the retry, an x402 version/header mismatch, or an expired proof (~60s validity window).
Security considerations
- User ownership. AgentCore is an authorized signer, not the wallet owner. The user grants delegation and can revoke it at any time. The user can also withdraw funds from the wallet at any time.
- Credential isolation. Privy credentials live in AgentCore Identity / Secrets Manager. Restrict the secret to the AgentCore Payments service role only.
- Session limits. Per-session
maxSpendAmount and short expiry bound runaway spending.
- HTTPS only. Reject non-HTTPS targets and block private/internal IP ranges to prevent SSRF.
- Audit. AgentCore Observability and CloudTrail capture every
ProcessPayment call; alarm on failed-payment spikes.
- Rotate the App Secret and authorization key on a regular schedule (e.g., every 90 days).
Further reading