February 2026 · ·

A2A x402 Agent Template: Build and Deploy a Paid AI Agent on AWS Lambda with Terraform

Building an AI agent that accepts on-chain payments means wiring together multiple protocols that don't know about each other. Agent discovery, payment verification, on-chain identity, deployment infrastructure — each one is a separate problem. So I built a template that handles the wiring. You clone it, customize three files, and deploy with one command. The result is a serverless agent on AWS Lambda that accepts USDC micropayments via x402 and speaks A2A for agent-to-agent communication.

This is the scaffold I wish I had when building SkillScan and the health-check agent. Both required the same boilerplate: CORS, payment middleware, agent card generation, dual entrypoints for Lambda and local dev. The template extracts all of that into a reusable starting point.


The Protocols

A2A

Google's Agent-to-Agent protocol gives agents a standard way to discover and talk to each other. Each agent publishes an agent card at /.well-known/agent-card.json describing its skills, supported protocols, and endpoint URL. Communication happens over JSON-RPC — agents send message/send to request work and get structured responses back. Without a standard like this, every agent integration is a custom one-off.

x402

The x402 protocol brings the HTTP 402 status code back to life. When a client hits a paid endpoint without payment, they get a 402 response with payment terms — price, network, asset, wallet address. The client signs a gasless EIP-3009 USDC transfer, retries with the signed authorization in an X-PAYMENT header, and a facilitator settles the transaction on-chain. No gas fees for the client, no blockchain code in your agent. I covered x402 in depth in the x402-cli post.

ERC-8004

ERC-8004 is on-chain agent identity. You mint an NFT whose metadata (hosted on IPFS) points to your agent's live endpoint and capabilities. Anyone can look up your agent by reading the token on-chain. It's optional — your agent works fine without it — but it enables trustless agent discovery without a centralized registry.

Here's what's under the hood:

Library Package What it does
Hono hono Web framework — runs on Lambda, Node.js, Bun, Cloudflare Workers
x402 @x402/hono @x402/core @x402/evm HTTP 402 payment protocol — clients pay with USDC, facilitator verifies
A2A @a2a-js/sdk Agent-to-Agent protocol — JSON-RPC for agent discovery and messaging
ERC-8004 agent0-sdk On-chain identity — mints NFT with IPFS metadata pointing to endpoint

Architecture Overview

Terraform manages all the AWS infrastructure: Lambda function, ECR image repository, Secrets Manager (private key), IAM roles, CloudWatch logs, and the Function URL endpoint. The Hono app runs inside a Docker container on Lambda (arm64), with endpoints split into free and paid.

graph TB
    Client["Client"] -->|"Request"| URL["Lambda Function URL"]
    subgraph AWS["AWS (Terraform-managed)"]
        URL --> Lambda["Lambda<br/>(arm64, Docker)"]
        ECR["ECR<br/>(image src)"] -.-> Lambda
        Lambda -.-> SM["Secrets Manager<br/>(private key)"]
        Lambda --> Hono["Hono App"]
        Hono --> Free
        Hono --> Paid
        subgraph Free["Free"]
            H["/health"]
            AC["agent-card"]
        end
        subgraph Paid["Paid"]
            API["/api/*"]
            A2A["/a2a"]
        end
    end
    Paid -->|"verify"| Fac["x402 Facilitator"]
    Fac -->|"settle"| Base["Base (USDC)"]

Endpoints split into free and paid:

Endpoint                                         Payment
──────────────────────────────────────────────────────────
GET  /health                                     Free
GET  /.well-known/agent-card.json                Free
POST /a2a — tasks/get, tasks/cancel              Free
POST /a2a — message/send, message/stream         $0.01 USDC
GET  /api/hello                                  $0.01 USDC

Read-only A2A methods are always free — agents need to discover each other without paying. Only work-producing methods (message/send, message/stream) require payment.


Getting Started

Before cloning, make sure you have these installed:

  • Node.js 22+ and npm
  • Docker — for building the Lambda container image
  • Terraform — for provisioning AWS infrastructure
  • AWS CLI — configured with credentials (aws configure)

Docker, Terraform, and AWS CLI are only needed for deployment. If you just want to run locally, Node.js is enough.

git clone https://github.com/wgopar/a2a-x402-agent-template.git
cd a2a-x402-agent-template
npm install

Create a Wallet

Your agent needs an Ethereum wallet to receive payments. The create-wallet script generates one for you:

npm run create-wallet -- my-agent

This generates a random private key, derives the wallet address, and writes both to two files: .env for local development and infra/terraform.tfvars for deployment. You don't need to touch either file manually.

After creating the wallet, fund it with testnet ETH from a Base Sepolia faucet. The wallet needs a small amount of ETH to cover gas when registering on-chain identity later. If you're only running locally or deploying without ERC-8004 registration, you can skip this step.


Run Locally

Start the dev server with hot reload:

npm run dev

This spins up a Hono server on localhost:3000 using tsx watch, so any file changes restart the server automatically. You can test the free endpoints right away:

# Health check
curl http://localhost:3000/health

# Agent card (A2A discovery)
curl http://localhost:3000/.well-known/agent-card.json

# Paid endpoint — returns 402 with payment terms
curl -i http://localhost:3000/api/hello

The /health and agent card endpoints return immediately. The /api/hello endpoint returns a 402 with payment terms — that's x402 working. In production, a client would sign a payment and retry. Locally, you can verify the 402 response includes the right price, network, and wallet address.


Customize Your Agent

Three files to edit. Everything else — middleware, A2A wiring, deployment config — stays untouched.

src/agent/skills.ts — Define skills

Skills describe what your agent can do. They show up in the agent card, so other agents (and humans) know what to ask for.

export const skills: AgentSkill[] = [
  {
    id: "my-skill",
    name: "My Skill",
    description: "What this skill does",
    tags: ["tag1", "tag2"],
    examples: ["Do the thing", "Another example"],
  },
];

src/agent/executor.ts — Implement logic

The executor is where your agent does its actual work. It receives the request context and publishes responses through an event bus.

export class MyExecutor implements AgentExecutor {
  async execute(
    requestContext: RequestContext,
    eventBus: ExecutionEventBus,
  ): Promise<void> {
    // Your agent logic here
    eventBus.publish(response);
    eventBus.finished();
  }
}

src/routes/api.ts — Add HTTP endpoints

If your agent needs regular HTTP endpoints alongside A2A, add them here. Anything under /api/* is automatically payment-gated.

api.get("/my-endpoint", (c) => {
  return c.json({ data: "your response" });
});

Update the price in src/app.ts if you need a different rate than the default $0.01 USDC.


How Payments Work

Now that you've seen the agent structure, here's what happens when a client hits a paid endpoint. The x402 protocol uses gasless payments — clients never submit blockchain transactions. They sign an off-chain EIP-3009 transferWithAuthorization, and a facilitator settles on-chain on their behalf.

sequenceDiagram
    participant C as Client
    participant A as Agent
    participant F as Facilitator
    participant B as Blockchain
    C->>A: Request (no payment)
    A-->>C: 402 + payment terms
    Note over C: Sign EIP-3009<br/>transferWithAuthorization<br/>(gasless, off-chain)
    C->>A: Retry + X-PAYMENT header
    A->>F: Verify + settle
    F->>B: Submit transfer
    F-->>A: Settlement proof
    A-->>C: Response + tx hash

The payment middleware (src/payments/x402.ts) wraps @x402/hono. You define routes with prices in src/app.ts, and the middleware handles 402 responses and payment verification automatically. Routes under /api/* are all paid. The /a2a endpoint is selective — a middleware checks the JSON-RPC method name and only gates message/send and message/stream.

The template ships configured for Base Sepolia (testnet) so you can develop without risking real funds. When you're ready for production, switch to Base mainnet by changing three env vars in .env and infra/terraform.tfvars:

Variable Base Sepolia (testnet) Base Mainnet
NETWORK eip155:84532 eip155:8453
RPC_URL https://sepolia.base.org https://mainnet.base.org
FACILITATOR_URL https://x402.org/facilitator https://api.cdp.coinbase.com/platform/v2/x402

After switching, redeploy with npm run deploy and re-register identity if applicable.


Deploy to Lambda

Once you're happy with your agent locally, deploy it:

npm run deploy

The deploy script does three things. First, it builds a Docker image using a multi-stage Dockerfile — esbuild bundles your TypeScript, and the final image targets Lambda's arm64 runtime. Second, it pushes the image to ECR. Third, it runs terraform apply, which creates all the AWS infrastructure in one shot.

graph TB
    A["Docker Build<br/>(multi-stage + esbuild)"] --> B["Docker Push<br/>(to ECR)"]
    B --> C["Terraform Apply<br/>(Lambda + IAM<br/>+ Secrets Mgr<br/>+ Function URL)"]
    C --> D["Live Endpoint<br/>(public HTTPS)"]

Terraform creates the Lambda function, an ECR repository, IAM roles, CloudWatch log group, and a public Function URL endpoint. Your private key goes into AWS Secrets Manager — not a Lambda environment variable — so it's encrypted at rest and accessed via IAM at runtime. The Lambda function URL gives you a public HTTPS endpoint with CORS already configured.


Register On-Chain Identity (Optional)

If you want your agent discoverable on-chain, register it with ERC-8004. This mints an NFT whose metadata (hosted on IPFS) points to your agent's live endpoint and capabilities.

# Add your Pinata JWT to .env, then:
npm run register

The script builds metadata from your agent config — name, description, skills, endpoint URL — uploads it to IPFS via Pinata, mints an NFT on Base Sepolia, and sets the token URI to the IPFS hash. Anyone can then look up your agent by reading the token on-chain: tokenURI(tokenId) → IPFS metadata → your agent's live endpoint.

graph LR
    A["Build metadata<br/>(name, skills,<br/>endpoint)"] --> B["Upload to IPFS<br/>(via Pinata)"]
    B --> C["Mint NFT<br/>(on-chain)"]
    C --> D["Set token URI<br/>(ipfs://...)"]

This step is optional — your agent works fine without it. But it enables trustless agent discovery without a centralized registry.


Resources