Cuando construimos agentes de IA que consumen APIs de terceros, el problema no suele ser el razonamiento del modelo, es el pago. Un agente puede descubrir que un endpoint cuesta 0.0005 USDC,
pero alguien tiene que firmar esa transacción, y normalmente eso significa poner una clave privada dentro del código del agente. Esa es exactamente la parte que no queremos hacer.
Por ello introducimos el protocolo x402 el cual reutiliza el código de estado HTTP que casi nadie usaba —402 Payment Required— para que un servidor pueda responder "esto cuesta X, págalo e intentalo de nuevo".
Amazon Bedrock AgentCore Payments se encarga del otro lado: gestiona la wallet y firma la transacción del lado del servidor, de modo que el agente nunca ve una clave privada y solo puede gastar dentro del presupuesto que le asignamos.
En este artÃculo desplegaremos un flujo completo: un seller que cobra por contenido detrás de CloudFront, un agente que detecta el http code 402 y paga, y una interfaz web para verlo funcionar. Todo sobre AWS.
Requisitos
- Node.js 24
- Python 3.11 o superior
- AWS CLI 2.x, con
aws sts get-caller-identityfuncionando - AWS CDK 2.x
- Docker corriendo, no solo instalado. El agente se despliega como imagen de contenedor que construimos localmente y subimos a ECR Cuenta de AWS:
- Se considerara la region us-east-1
- Contar con credenciales que tengan el suficiente acceso a servicios como cloudfront, cloudwatch, s3, amazon bedrock agent core, lambda
- Acceso a modelos de Bedrock habilitado. Conviene confirmarlo antes de desplegar cualquier cosa:
aws bedrock-runtime converse \
--model-id us.anthropic.claude-sonnet-4-5-20250929-v1:0 \
--messages '[{"role":"user","content":[{"text":"hola"}]}]' \
--inference-config '{"maxTokens":10}'
Si esto responde con un AccessDeniedException que menciona aws-marketplace:Subscribe, el acceso al modelo no está habilitado en la cuenta. Se arregla en la consola, en Bedrock → Model access
- CDK bootstrapeado una vez por cuenta y región:
npx cdk bootstrap aws://<ACCOUNT_ID>/us-east-1
- Coinbase Developer Platform:
AgentCore Payments firma las transacciones a través de un proyecto de Coinbase CDP, asà que necesitamos configurarlo antes de empezar.
- Una cuenta en portal.cdp.coinbase.com
- Una API key (
CDP_API_KEY_IDyCDP_API_KEY_SECRET) y un wallet secret (CDP_WALLET_SECRET)
Delegated signing habilitado en las polÃticas del proyecto. Este es el requisito que más fácil se pasa por alto. Sin él, todas las llamadas a
ProcessPaymentfallan conDelegated signing is not enabled for your Coinbase project.

Un correo al que tengamos acceso. Al crear el payment instrument, CDP envÃa un enlace de activación de la wallet, y hasta que no lo abramos la wallet no puede firmar.
Fondos de prueba:
Utilizaremos faucet.circle.comuna testnet, que nos permitirá fondear nuestra wallet.
La arquitectura propuesta
Hay dos distribuciones de CloudFront una que expone nuestro frontend y nuestro backend es la API con paywall que consume el agente.
Navegador
|
v
CloudFront + S3 (interfaz web)
|
v
API Gateway (lÃmite duro de 29 segundos)
|
v
Lambda (proxy: InvokeAgentRuntime)
|
v
AgentCore Runtime (contenedor con el agente Strands)
|
+--> AgentCore Payments --> Coinbase CDP (wallet, firma)
|
+--> CloudFront + Lambda@Edge (vendedor: verifica x402, devuelve 402 o el contenido)
|
v
S3 (contenido de pago)
Lo importante de este diseño es qué componente conoce qué:
- El navegador no conoce la URL del seller. Le pide al agente una ruta como
/api/weather-data, y el agente la resuelve contra su propioSELLER_API_URL. Asà el origen del pago nunca se puede manipular desde el cliente. - El agente no tiene claves privadas. Llama a
ProcessPaymenty recibe una prueba de pago. - La sesión de pago define el presupuesto. El agente opera bajo un rol de IAM(
ProcessPaymentRole) que solo puede ejecutar pagos dentro de ese lÃmite, y no puede crear sesiones ni modificarlo.
El flujo son tres pasos, y cada uno es una llamada independiente al agente:
-
request_content("/api/weather-data")devuelve402con elx402_payload -
process_payment(x402_payload)devuelve estadoPROOF_GENERATED -
request_content_with_payment("/api/weather-data")re-intenta con la cabeceraPAYMENT-SIGNATUREy devuelve200con el contenido
Los separamos por una razón práctica: API Gateway corta la conexión a los 29 segundos. Si le pedimos al agente que haga los tres pasos en un solo prompt, la respuesta HTTP se pierde aunque el agente termine el trabajo.
El contenido del vendedor viene de dos fuentes. Tres endpoints devuelven datos embebidos en la propia Lambda@Edge y tres los leen de S3:
-
/api/weather-data— 0.0005 USDC, embebido -
/api/premium-article— 0.001 USDC, embebido -
/api/market-analysis— 0.002 USDC, embebido -
/api/tutorial— 0.003 USDC, S3 -
/api/research-report— 0.005 USDC, S3 -
/api/dataset— 0.01 USDC, S3
Walkthrough
Paso 1: desplegar el seller
Crea la distribución de CloudFront, el verificador x402 como Lambda@Edge y el bucket de contenido.
cd seller-infrastructure
cp .env.example .env
En ese .env hay que poner PAYMENT_RECIPIENT_ADDRESS, la wallet que va a recibir los pagos. El stack falla a propósito si no está definida: desplegar un paywall sin destinatario explÃcito significarÃa cobrar a favor de otra persona.
npm install
npx cdk deploy
El despliegue de CloudFront con Lambda@Edge es lento; entre 10 y 15 minutos es normal. Del output nos interesa DistributionUrl, que es la URL del vendedor.
Ahora subimos el contenido de los tres endpoints que leen de S3:
./scripts/upload-content.sh $(aws cloudformation describe-stacks --stack-name X402SellerStack \
--query "Stacks[0].Outputs[?OutputKey=='ContentBucketName'].OutputValue" --output text)
Verificamos que el paywall responde:
curl -s -o /dev/null -w '%{http_code}\n' https://<seller>.cloudfront.net/api/weather-data
Debe devolver 402. Si devuelve 200, estamos apuntando a la distribución equivocada.
Paso 2: desplegar los roles de IAM del pagador
Los recursos de pago del paso 3 necesitan ARNs de roles que crea este stack.
cd ../payer-infrastructure
npm install
npx cdk deploy X402PayerAgentStack
Hay que nombrar el stack explÃcitamente porque la aplicación define dos; un cdk deploy a secas falla preguntando cuál queremos. El segundo, X402ObservabilityStack, trae dashboards y alarmas de CloudWatch y es opcional.
Del output guardamos ProcessPaymentRoleArn, ResourceRetrievalRoleArn y AgentRuntimeRoleArn.
Paso 3: crear los recursos de AgentCore Payments
Un solo script crea el credential provider, el payment manager, el connector, el instrument (la wallet) y la sesión con presupuesto.
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 del paso 2>
export USER_EMAIL=tu@correo.com
export USER_ID=x402-demo-user
python scripts/setup_payments.py
El script imprime una URL de activación de la wallet. Hay que abrirla y completar la activación, o la wallet no podrá firmar nada. Después imprime los valores que van al .env del
siguiente paso.
Antes de seguir, fondeamos la wallet con USDC de Base Sepolia desde el faucet de Circle. La dirección es la que aparece en la salida del script.
Paso 4: configurar y desplegar el agente
cp .env.example .env
Los valores mÃnimos:
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=<del paso 3>
PAYMENT_SESSION_ID=<del paso 3>
PAYMENT_INSTRUMENT_ID=<del paso 3>
PROCESS_PAYMENT_ROLE_ARN=<ProcessPaymentRoleArn del paso 2>
USER_ID=x402-demo-user
SELLER_API_URL va sin barra final: las herramientas de contenido reciben una ruta y la concatenan sobre esta base. AGENT_RUNTIME_ARN se deja vacÃo; el script de despliegue lo escribe de vuelta en el archivo.
python scripts/deploy_to_agentcore.py
Esto construye la imagen, la sube a ECR y crea o actualiza el runtime. Solo las variables que el script tiene en su lista blanca llegan al entorno del runtime, lo que en la práctica significa que
si agregamos una variable nueva al .env y no la agregamos a esa lista, el agente no la va a ver.
Paso 5: desplegar la interfaz web
cd ../web-ui-infrastructure
npm install
cd ../web-ui && npm install && cd ../web-ui-infrastructure
WALLET_ADDRESS=<la wallet del paso 3> \
AGENT_RUNTIME_ARN=<del .env del paso 4> \
./scripts/deploy.sh
El script despliega el stack, incrusta la URL de API Gateway resultante en el build del frontend y lo sube a S3. Las dos variables importan: AGENT_RUNTIME_ARN es lo que invoca la Lambda, y WALLET_ADDRESS es lo que muestra el panel de wallet.
Abrimos la URL de WebUiUrl y probamos: elegimos un contenido, Request Content debe mostrar el 402 con el detalle del pago, y Confirm Payment debe devolver el contenido.
También se puede probar directo contra la API:
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"}'
Ejemplo
Agregamos fondos nuestro wallet:
Desde nuestro frontend verificamos la direccion del wallet y de los fondos.
Seleccionamos el contenido premium de nuestro agente
Errores comunes y cómo depurarlos
Estos son los que nos costaron tiempo real, con el mensaje que aparece y la causa.
Received error (422) from runtime, y CloudWatch no muestra nada más que el 422. Normalmente significa que la llamada a invoke_agent_runtime se hizo sin contentType. Ese parámetro es opcional en boto3, asà que la petición llega al contenedor sin content type JSON y FastAPI rechaza el body antes de que se ejecute nuestro handler. Por eso no hay logs de la aplicación: el 422 lo genera el framework. Se resuelve pasando contentType='application/json'.
Invocation of model ID ... with on-demand throughput isn't supported. El BEDROCK_MODEL_ID es un ID de foundation model y hace falta un inference profile de cross-region, es decir el mismo ID con el prefijo us.. Conviene revisar cuáles están activos:
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. La polÃtica otorga el ARN del runtime, pero la autorización ocurre contra el subrecurso runtime-endpoint. Hay que permitir el ARN y también <arn>/*.
Missing or invalid paymentManagerArn / paymentSessionId / paymentInstrumentId. Las variables están en el .env local pero nunca llegaron al entorno del runtime. Se verifica con get-agent-runtime y se mira qué hay realmente en environmentVariables.
Un 403 con una página HTML en lugar de JSON al reintentar con la prueba de pago. El reintento se hizo con POST. El comportamiento /api/* de CloudFront permite solo HEAD, GET, OPTIONS, asà que CloudFront rechaza el método antes de que el verificador x402 llegue a ejecutarse. La pista está en el formato: si el error es HTML y no JSON, no lo generó nuestra Lambda.
El .env parece ignorado. load_dotenv() no sobreescribe variables que ya existen en el entorno. Un export viejo en la
terminal gana silenciosamente, y el caso peor es una variable con el valor literal None, que en Python es una cadena verdadera y por lo tanto parece configurada. Revisar con env | grep AGENT_.
Endpoint request timed out desde el navegador. Es el lÃmite de 29 segundos de API Gateway. El agente sigue trabajando en segundo plano, lo que se perdió es la respuesta HTTP. Por eso la interfaz hace una llamada por paso.
Para ver qué pasa realmente dentro del agente:
aws logs tail /aws/bedrock-agentcore/runtimes/<runtime-id>-DEFAULT --since 10m --format short \
| grep -v "GET /ping"
El filtro de /ping no es cosmético: AgentCore hace health check del contenedor unas dos veces por segundo y esas lÃneas entierran todo lo demás.
Recomendaciones
- Tratar la sesión de pago como lo que es, un presupuesto con vencimiento. Se crea con
maxSpendAmountyexpiryTimeInMinutes, y cuando expira hay que crear otra y actualizarPAYMENT_SESSION_ID. Conviene consultaravailableLimitsantes de asumir que un pago falló por otra razón. - No dejar que el frontend construya URLs de pago. Que envÃe rutas y que el agente las resuelva contra su propia configuración; si el agente acepta URLs absolutas, alguien puede redirigir el pago a otro origen.
- Mantener el rol del agente reducido a
ProcessPayment. Si necesitamos consultar saldos o instrumentos desde un backend, que sea con otras credenciales y no ampliando ese rol. - Actualizar el runtime en lugar de recrearlo. Si el despliegue borra y vuelve a crear el runtime, el ARN cambia y todo lo que lo tenÃa guardado —la Lambda de la interfaz, el
.env— queda apuntando a un recurso que ya no existe. - Empezar siempre en testnet con presupuestos pequeños. Un agente que paga automáticamente y falla al recuperar el contenido igual gastó el dinero.
El código completo del proyecto está en el repositorio, con el QUICKSTART.md que cubre el despliegue y el desmontaje de los recursos que CDK no administra: el runtime, la imagen de ECR y los recursos de AgentCore Payments.







Top comments (0)