# How to Accept AI Agent Payments with x402 + PayWatcher

*PayWatcher · April 2026*

By [PayWatcher](https://paragraph.com/@paywatcher) · 2026-04-05

x402, usdc, ai, agents, web3dev, stablecoins, base, arbitrum, buildonbase, paymentinfrastructure, tutorial

---

The x402 protocol is quietly becoming the standard for machine-to-machine payments. If you're building an API that AI agents consume — whether that's a data endpoint, a compute service, or a gated resource — x402 lets agents pay per request in USDC without human intervention.

  

This article walks through exactly how to implement x402 on your server using PayWatcher as the verification layer.

* * *

What is x402?
-------------

x402 is an extension of HTTP that brings the long-forgotten 402 Payment Required status code back to life. The flow is simple:

1.  Client requests a resource
    
2.  Server responds with `HTTP 402` + a payment descriptor (amount, address, chain)
    
3.  Client sends USDC on-chain
    
4.  Client retries the request with the transaction hash
    
5.  Server verifies the payment and returns the resource
    

The protocol was pioneered by Coinbase and is gaining adoption across AI agent frameworks including MCP servers, Coinbase AgentKit, and custom agent runtimes.

* * *

The Verification Problem
------------------------

The tricky part of x402 is step 5. Your server needs to answer one question:

> "Did this transaction actually happen — with the right amount, to the right address, on the right chain?"

You have two options:

**Option A: Roll your own** — query an RPC node directly, parse ERC-20 Transfer logs, handle confirmations, manage multi-chain RPCs. This takes days, not hours.

**Option B: Delegate to PayWatcher** — one API call, $0.05 flat, works across Base, Arbitrum, Ethereum, Optimism, and Polygon.

This tutorial uses Option B.

* * *

Prerequisites
-------------

*   A PayWatcher account (request access at [paywatcher.dev](http://paywatcher.dev))
    
*   Your PayWatcher API key
    
*   Your deposit address configured in PayWatcher dashboard
    
*   Node.js + Express (or any HTTP framework)
    
*   The `coinbase/x402` npm package (optional but recommended)
    

* * *

Step 1: Set Up Your Express Server
----------------------------------

    import express from "express"
    
    const app = express()
    app.use(express.json())
    
    const PAYWATCHER_API_KEY = process.env.PAYWATCHER_API_KEY!
    const DEPOSIT_ADDRESS = process.env.DEPOSIT_ADDRESS!
    const RESOURCE_PRICE = "1.00" // $1.00 USDC
    

* * *

Step 2: Return HTTP 402 with Payment Descriptor
-----------------------------------------------

When a request comes in without payment, respond with a 402 and tell the client what to pay:

    app.get("/api/report/:id", async (req, res) => {
      const paymentHeader = req.headers["x-payment"]
    
      if (!paymentHeader) {
        // No payment — return 402 with descriptor
        return res.status(402).json({
          scheme: "exact",
          network: "base",
          maxAmountRequired: "1000000", // $1.00 in USDC atomic units (6 decimals)
          resource: req.path,
          description: "Access to report",
          mimeType: "application/json",
          payTo: DEPOSIT_ADDRESS,
          maxTimeoutSeconds: 300,
          asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base
          extra: { name: "USDC", version: "2" }
        })
      }
    
      // Payment header present — verify it
      const { txHash, network } = JSON.parse(paymentHeader as string)
      const verified = await verifyWithPayWatcher(txHash, network)
    
      if (!verified) {
        return res.status(402).json({ error: "Payment verification failed" })
      }
    
      // Payment verified — return the resource
      return res.json({ data: await getReport(req.params.id) })
    })
    

* * *

Step 3: Verify with PayWatcher
------------------------------

    async function verifyWithPayWatcher(
      txHash: string,
      network: string
    ): Promise<boolean> {
      const response = await fetch("https://api.paywatcher.dev/v1/payments/verify", {
        method: "POST",
        headers: {
          "x-api-key": PAYWATCHER_API_KEY,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          tx_hash: txHash,
          network: network,
          amount: RESOURCE_PRICE,
          to: DEPOSIT_ADDRESS,
          tolerance_seconds: 300,
        }),
      })
    
      const { data } = await response.json()
      return data.verified === true
    }
    

That's it. Three fields in, one boolean out.

* * *

Step 4: Handle retry_after_seconds
----------------------------------

PayWatcher returns a `retry_after_seconds` hint when a transaction is found but not yet confirmed:

    async function verifyWithPayWatcher(txHash: string, network: string) {
      const response = await fetch("https://api.paywatcher.dev/v1/payments/verify", {
        method: "POST",
        headers: {
          "x-api-key": PAYWATCHER_API_KEY,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          tx_hash: txHash,
          network: network,
          amount: RESOURCE_PRICE,
          to: DEPOSIT_ADDRESS,
        }),
      })
    
      const { data } = await response.json()
    
      if (!data.verified && data.retry_after_seconds) {
        // Transaction found but not confirmed yet — tell the client to retry
        return {
          verified: false,
          retryAfter: data.retry_after_seconds,
          reason: data.reason,
        }
      }
    
      return { verified: data.verified, retryAfter: null, reason: data.reason }
    }
    

Return a `Retry-After` header so the agent knows when to try again:

    if (!result.verified && result.retryAfter) {
      return res
        .status(402)
        .set("Retry-After", String(result.retryAfter))
        .json({ error: "Payment pending confirmation", reason: result.reason })
    }
    

* * *

Step 5: Use PayWatcher's x402 Descriptor (Optional)
---------------------------------------------------

If you want PayWatcher to generate the exact x402 descriptor for you — including the correct USDC contract address per chain and amount in atomic units — add `x402: true` to your payment intent:

    const intentResponse = await fetch("https://api.paywatcher.dev/v1/payments", {
      method: "POST",
      headers: {
        "x-api-key": PAYWATCHER_API_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        amount: RESOURCE_PRICE,
        network: "base",
        x402: true,
      }),
    })
    
    const { data } = await intentResponse.json()
    
    // Return the pre-built x402 descriptor directly
    return res.status(402).json(data.x402)
    

This is the easiest path if you want multi-chain support without hardcoding contract addresses per network.

* * *

Complete Flow
-------------

    Client                    Your Server              PayWatcher
      │                           │                        │
      │── GET /api/report/42 ────>│                        │
      │<─ 402 + descriptor ───────│                        │
      │                           │                        │
      │  [sends USDC on-chain]    │                        │
      │                           │                        │
      │── GET /api/report/42 ────>│                        │
      │   x-payment: {txHash}     │                        │
      │                           │── POST /verify ───────>│
      │                           │<─ { verified: true } ──│
      │<─ 200 + data ─────────────│                        │
    

* * *

Supported Chains
----------------

PayWatcher verifies USDC payments on:

Chain

Network slug

USDC Contract

Base

`base`

0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913

Arbitrum

`arbitrum`

0xaf88d065e77c8cC2239327C5EDb3A432268e5831

Ethereum

`ethereum`

0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48

Optimism

`optimism`

0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85

Polygon

`polygon`

0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359

Same API call, same $0.05 fee, regardless of chain.

* * *

What You Get
------------

*   **No RPC infrastructure** — PayWatcher handles all blockchain queries
    
*   **Multi-chain out of the box** — change `network` in your request, nothing else
    
*   **Replay attack protection** — `tolerance_seconds` rejects old transactions
    
*   **Confirmation guarantees** — PayWatcher waits for chain-specific required confirmations before returning `verified: true`
    
*   **$0.05 flat** — not 0.5–1% of the transaction value
    

* * *

Next Steps
----------

*   Get access at [paywatcher.dev](http://paywatcher.dev)
    
*   Read the full API reference in the docs
    
*   Questions? [hello@paywatcher.dev](mailto:hello@paywatcher.dev)

---

*Originally published on [PayWatcher](https://paragraph.com/@paywatcher/how-to-accept-ai-agent-payments-with-x402-paywatcher)*
