DEV Community

Cover image for Descubre agentes transaccionales con Amazon Bedrock AgentCore Payments
David💻
David💻 Subscriber

Posted on

Descubre agentes transaccionales con Amazon Bedrock AgentCore Payments

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-identity funcionando
  • 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}'
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
  • 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_ID y CDP_API_KEY_SECRET) y un wallet secret (CDP_WALLET_SECRET)

secretkey

  • 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 ProcessPayment fallan con Delegated signing is not enabled for your Coinbase project.
    delegated

  • 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)
Enter fullscreen mode Exit fullscreen mode

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 propio SELLER_API_URL. Así el origen del pago nunca se puede manipular desde el cliente.
  • El agente no tiene claves privadas. Llama a ProcessPayment y 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:

  1. request_content("/api/weather-data") devuelve 402 con el x402_payload
  2. process_payment(x402_payload) devuelve estado PROOF_GENERATED
  3. request_content_with_payment("/api/weather-data") re-intenta con la cabecera PAYMENT-SIGNATURE y devuelve 200 con 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Verificamos que el paywall responde:

curl -s -o /dev/null -w '%{http_code}\n' https://<seller>.cloudfront.net/api/weather-data
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

ecr

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
Enter fullscreen mode Exit fullscreen mode

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.

frontend

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"}'
Enter fullscreen mode Exit fullscreen mode

Answer

Ejemplo

Agregamos fondos nuestro wallet:

USDC

Desde nuestro frontend verificamos la direccion del wallet y de los fondos.

WalletFondos

Seleccionamos el contenido premium de nuestro agente

PremiumContent
Procedemos con el pago

Payment confirmation

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"
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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 maxSpendAmount y expiryTimeInMinutes, y cuando expira hay que crear otra y actualizar PAYMENT_SESSION_ID. Conviene consultar availableLimits antes 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)