Wire protocol

The 402 → sign → pay → receipt flow, and the exact messages on the wire.

AgentPay is x402-compatible from day one. The wire format is pipe-separated, with no JSON and no whitespace ambiguity, so signatures are unambiguous.

The flow

  1. The agent calls a merchant API. The merchant returns 402 Payment Required with X-Payment-* headers describing the price and resource.
  2. The agent signs a canonical message with its Ed25519 key and posts it to the gateway /pay.
  3. The gateway verifies the signature, checks the rules, moves the money, and returns a gateway-signed receipt.
  4. The agent retries the merchant with an X-Payment-Receipt header. The merchant verifies the receipt and serves the response.

Canonical messages

The agent signs this exact string:

agentpay.payment.v1|<agent_id>|<merchant_id>|<amount_cents>|USD|<resource>|<nonce>

The gateway signs the receipt it returns:

agentpay.receipt.v1|<receipt_id>|<agent_id>|<merchant_id>|<amount_cents>|USD|<resource>|<nonce>|<expires_at>

All amounts are integer cents on the wire and in storage. Dollars appear only at SDK ergonomics.

Verifying a receipt

The merchant does not need to call back to the gateway. It fetches the gateway public key once from /pubkey and verifies the Ed25519 signature locally.

import { verifyReceipt } from "@agentpay/merchant";

const ok = verifyReceipt(receipt, gatewayPubkey); // true / false, no round trip

Replay and expiry

Receipts expire in minutes (expires_at is part of the signed message) and are single-use. The agent generates a fresh nonce per request, so a receipt cannot be replayed later.

Identity (KYA)

Each agent also carries a signed identity passport at GET /agent/:id/passport: its public key, owner, mandate, and a gateway signature. Merchants can require verified agents at the door. See How it works.