Skip to main content

x402-make-payments

Example payment client — wraps fetch to pay for HTTP-gated resources automatically, the way an autonomous agent would.
This walks through an example client that behaves like an agent: it requests an x402-gated resource, receives a 402, and pays for it automatically with no human in the loop.
The client holds its own signing key, so it pays as the participant that key belongs to. That is how the 402 flow works — it checks the signature, not who is behind the software. Establishing who is answerable when software spends for someone else is Know Your Agent, a separate question.

Prerequisites

npm is included with Node.js.
Verify:
Your agent needs a wallet that holds the source token (e.g. USDC on Base Sepolia) on the chain the merchant accepts.Export its private key — use a testnet-only wallet, never one holding real funds. See Set up a testnet wallet.If you are testing locally against the merchant’s in-process stub facilitator, any private key will work since no real transaction is submitted.
For real on-chain settlement, the Permit2 contract must be approved to spend your source token.Run this once on the source chain:
Skip this step when testing locally — the stub facilitator does not check allowances.

Prerequisites for real settlement

Local stub runs need no funds. Before you point the client at testnet or mainnet, confirm your payer wallet has the source token, native gas for the one-time Permit2 approve(), and an active allowance — see Prerequisites.

1. Clone and install

2. Configure

Set PRIVATE_KEY in .env to your agent wallet’s 0x-prefixed key — it’s the only value you need to run against the local merchant in stub mode. The client’s other variables (MERCHANT_URL, RPC_URL) are documented in the example README; the corridor itself is the merchant’s config, not the client’s.
Switching wallets, environments, or corridors? If you exported PRIVATE_KEY in your shell earlier (export PRIVATE_KEY=0x…), that value overrides .env — dotenv does not replace variables already set in your environment, so the client can silently sign with the wrong wallet after you edit .env. Run unset PRIVATE_KEY before npm run pay so the value from .env is used.

3. Start the merchant (if running locally)

Follow Agentic receives to start the example merchant first — it runs an in-process stub facilitator, so there’s nothing else to start:
The merchant is a separate app, so it needs its own npm install (the clone step only installed x402-make-payments). To install all four example apps at once instead, run npm run install:all from the repo root.

4. Run the client

Back in x402-make-payments:
Expected output:

What’s happening

Looking at src/client.ts and src/purchase.ts:
  1. registerAtumEscrowScheme teaches the x402 client how to construct and sign Atum-scheme payment credentials.
  2. wrapFetchWithAtumPayment wraps the native fetch — your agent calls it with a purchase identifier (paymentIdentifier) so every re-attempt names the same payment.
  3. On 402, the wrapper reads the payment requirements, signs a Permit2 authorization, and retries once with PAYMENT-SIGNATUREwrapFetchWithAtumPayment itself makes exactly that one attempt and returns, whatever the outcome. It does not loop.
  4. The retry-until-settled behavior is a layer above it: src/purchase.ts’s payPurchase() is application code that calls the wrapped fetch in a loop, re-attempting the same purchase (fresh 402, re-signed credential, same paymentIdentifier) every few seconds while the response reports settlement_pending, until settlement reaches a terminal outcome. src/client.ts wires the two together — payPurchase(() => pay(...)). Integrating directly against the SDK instead of reusing this example means writing your own version of that loop. See Idempotency.
The wallet signs a Permit2 authorization — it does not broadcast a transaction. The settlement vault deposit only executes on-chain when the facilitator calls settle.

Going to testnet or mainnet

Real settlement means funding your agent’s wallet, approving the source token (Permit2), and pointing both merchant and client at Atum-provided URLs. See the example README for .env values, funding amounts, and on-chain verification — its Going to testnet or mainnet section walks through that app. New to Atum’s environments? See Choose an environment for which one to target and what to expect from production-testnet. Run through the pre-flight checklist before your first real payment.
Cross-chain settlement can exceed the gateway’s ~30s synchronous window. When it does, the facilitator reports settlement_pending, and it’s payPurchase() — not wrapFetchWithAtumPayment itself — that re-attempts the same purchase until it settles. That re-attempt cannot charge twice, however many times it runs. If you integrate directly rather than reusing purchase.ts, you need your own version of that retry loop. Confirm the payment on the destination chain before paying again under a new purchase identifier. See Idempotency.

Next steps