Guides

Authenticate x402 agents with wallet verifiers

Bind x402-paying wallets to stable agent identities — add CAIP-2 wallet verifiers, resolve a payer wallet to an agent, and scope what it may do.

Last updated 2026-06-12

x402 is Coinbase’s open payment protocol built on HTTP status code 402. When a client requests a paid resource, the server replies 402 Payment Required with a machine-readable challenge; the client signs a payment from a crypto wallet (on EVM chains, an EIP-3009 signed transfer) and replays the request with a PAYMENT-SIGNATURE header. A facilitator verifies the signature and settles it on-chain. No account creation, no credit card form, no human in the loop.

That last property is why x402 is becoming the default rail for AI agents buying API access: an agent can discover your API, pay for a request, and consume the response entirely autonomously. But it ships with an identity gap.

The identity gap

A verified payment signature tells you the money is good. It tells you nothing about who is calling:

  • A wallet is not an identity. The same agent may pay from rotating wallets; one operator may fund many agents from one treasury wallet.
  • Payment is not permission. “Paid $0.01” doesn’t answer: which endpoints may this caller use? At what rate? On behalf of which of your customers?
  • You can’t audit an address. Logs full of 0x36f2... are useless when a customer asks “what did our agent do last Tuesday?”

The fix is to give the paying wallet a stable identity: an AuthPI agent (agt_ prefix) with a wallet verifier — a registered CAIP-2 wallet address that maps back to the agent, its scopes, and its organization memberships.

Division of labor, plainly: your x402 facilitator (e.g. Coinbase’s) verifies payment signatures and settles them on-chain — AuthPI does not process payments and does not verify on-chain signatures. AuthPI stores and validates wallet verifiers, maintains the wallet → agent mapping, and gives agents scopes, org membership, OAuth tokens, and an audit trail. You combine the two: the facilitator answers “is the payment real?”, AuthPI answers “who is paying, and what may they do?”

Not to be confused with AuthPI’s own billing. AuthPI also accepts x402 payments — that’s how customers fund their AuthPI account, documented in Credits & Billing. This page is the other direction: authenticating x402-paying agents that call your product.

Step 1 — Create the agent

Agents live under an issuer. Create one with the Core API (API keys authenticate with HTTP Basic auth — key ID as username, secret as password):

curl -X POST "https://api.authpi.com/v1/accounts/$ACCOUNT_ID/issuers/$ISSUER_ID/agents" \
  -u "$AUTHPI_KEY_ID:$AUTHPI_KEY_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "checkout-agent",
    "scopes": ["invoices:read", "orders:create"]
  }'

The response wraps the agent in data — note the agt_ ID, which is the stable subject you’ll see in logs and tokens:

{
  "data": {
    "id": "agt_9f2c1a7e4b3d48f6a0c5e8d2b7a91c3f",
    "issuer_id": "i_8fK2mQp4xR7tWz",
    "name": "checkout-agent",
    "status": "active",
    "scopes": ["invoices:read", "orders:create"],
    "created_at": 1781222400000
  }
}

Step 2 — Add a wallet verifier

Register the wallet the agent pays from. The body takes the address and the network as separate fields — network is a CAIP-2 chain ID (eip155:8453 is Base mainnet), so together they form the CAIP-10 account eip155:8453:0x36f2...:

curl -X POST "https://api.authpi.com/v1/accounts/$ACCOUNT_ID/issuers/$ISSUER_ID/agents/$AGENT_ID/verifiers" \
  -u "$AUTHPI_KEY_ID:$AUTHPI_KEY_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "wallet",
    "name": "base-mainnet-treasury",
    "address": "0x36f2eAaB9e428DA1f4f24DDa75d2acD4cd9b7B17",
    "network": "eip155:8453"
  }'
{
  "data": {
    "id": "v_5e8d2b7a91c3f49f2c1a7e4b3d48f6a0",
    "agent_id": "agt_9f2c1a7e4b3d48f6a0c5e8d2b7a91c3f",
    "type": "wallet",
    "status": "active",
    "name": "base-mainnet-treasury",
    "credential": { "address": "0x36f2eAaB9e428DA1f4f24DDa75d2acD4cd9b7B17", "network": "eip155:8453" },
    "created_at": 1781222400000
  }
}

Addresses are matched case-insensitively, and each (network, address) pair can only be registered once per agent. An agent holds up to 20 verifiers, so one agent can own wallets on several chains (Base mainnet and Sepolia, say) under a single identity.

Step 3 — Resolve an incoming wallet to its agent

When a wallet verifier is added, AuthPI writes a reverse-lookup record to its edge KV store keyed wallet:{network}:{address} (address lowercased) pointing to { agent_id, issuer_id, verifier_id }. Removing the verifier — or deleting the agent — removes the record, so the index never serves stale mappings.

Today you consume that mapping through the verifiers API and webhooks rather than a dedicated lookup endpoint: wallet verifiers expose their address and network in GET .../agents/{agent_id}/verifiers (secret verifiers never expose their hash), and the agent.verifier.added / agent.verifier.removed events tell you the moment a mapping changes. Build the same lowercased key on your side:

// payer = the wallet that signed the x402 payment on this request,
// as reported by your facilitator's settlement response
const key = `${payer.network}:${payer.address.toLowerCase()}`; // "eip155:8453:0x36f2..."
const agentId = walletIndex.get(key); // your map, synced from the verifiers API + webhooks

if (!agentId) {
  return new Response("Payment accepted from unknown wallet — no agent registered", { status: 403 });
}
// agentId is a stable agt_ subject: rate-limit it, scope-check it, log it

The payment proves funds; the resolved agt_ identity carries everything else — scopes, status (suspend an agent and act on it immediately, no wallet blacklisting), and the audit trail.

Step 4 — Add a secret verifier for full OAuth tokens

Wallet resolution covers pay-per-request calls. When the agent needs a real access token — to call non-paid endpoints, or to carry scopes verifiable by any service — add a secret verifier and use the OAuth client_credentials grant:

curl -X POST "https://api.authpi.com/v1/accounts/$ACCOUNT_ID/issuers/$ISSUER_ID/agents/$AGENT_ID/verifiers" \
  -u "$AUTHPI_KEY_ID:$AUTHPI_KEY_SECRET" \
  -H "Content-Type: application/json" \
  -d '{ "type": "secret", "name": "cc-grant" }'

The response includes the plaintext secret once — store it now; only a SHA-256 hash is kept. The agent then mints tokens directly from your issuer’s token endpoint:

curl -X POST "https://idp.authpi.com/$ISSUER_ID/token" \
  -d "grant_type=client_credentials" \
  -d "client_id=$AGENT_ID" \
  -d "client_secret=$AGENT_SECRET" \
  -d "scope=invoices:read"

Agent tokens are deliberately short-lived — 5 minutes — and carry a dat claim of {"type": "agent"} so your services can distinguish agent tokens from human identity tokens with a typed check instead of a sub-prefix heuristic. Requested scopes must be a subset of the agent’s scopes; omit scope to get the full allowed set. The 5-minute TTL is the revocation primitive: suspend the agent and no new tokens mint, so a change propagates within one TTL window. The full flow is walked through in the agents quickstart.

Step 5 — Scope and limit the agent with org membership

If your product is multi-tenant, attach the agent to the customer’s organization. The members endpoint accepts agt_ IDs as member_id:

curl -X POST "https://api.authpi.com/v1/accounts/$ACCOUNT_ID/issuers/$ISSUER_ID/organizations/$ORG_ID/members" \
  -u "$AUTHPI_KEY_ID:$AUTHPI_KEY_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "member_id": "agt_9f2c1a7e4b3d48f6a0c5e8d2b7a91c3f",
    "scopes": ["billing:read"]
  }'

The membership is the tenancy record: the agent appears in the org’s member list with member_type: "agent" and its own per-org scopes, and you can remove it to cut access to that tenant without touching the agent’s wallets or secrets. One honest caveat: client_credentials tokens carry only the agent’s own scopes — they don’t embed org claims — so enforce per-org scopes server-side by checking the agent’s membership via the members API.

Next steps