Inspiration
AI agents are starting to buy, sell and pay each other. A hotel's purchasing agent will soon pay its coffee supplier's sales agent with no person in the loop.
The industry is starting to verify the buyer's agent: AP2 mandates, Visa's Trusted Agent Protocol and Mastercard Agent Pay all help a merchant trust the agent that is paying. Almost nobody verifies the other direction: is the agent asking to be paid really allowed to collect for that business, into that account?
That gap is exactly where business payments already fail. Business email compromise cost US businesses $2.77 billion in 2024 (FBI IC3), mostly through changed bank details and impersonated suppliers. An Arup employee sent US$25 million after a deepfake video call with a fake "CFO". Agents make these attacks faster and cheaper, and prompt injection adds a new one: hidden text that talks an agent into buying something it shouldn't.
So we asked one question, from both sides: is this AI agent authorized to make or collect this payment?
What it does
ProxyProof sits between a buyer's agent and a seller's agent. Before any money moves, it checks both sides independently:
- Identity. Every request is signed with the agent's own Ed25519 key and answers a single-use challenge, so replayed or stale requests fail.
- Authority. Each business signs what its own agent may do: the buyer's mandate (what, how much, from whom) and the seller's delegation (what it may invoice, and the only account it may be paid into).
- Live status. Expiry and revocation are read live at every step. If the status service is down, the payment is held, never allowed from a cache.
- Same terms. Both agents must sign the same canonical terms hash.
The decision is Allow, Hold, Review or Deny, always with a reason code. Allow issues a single-use, five-minute permit, and only that permit can open a PayPal Orders v2 sandbox checkout. It counts as paid only when PayPal confirms the capture.
The live demo is a scenario lab. Three everyday requests (order coffee, pay a monthly bill, pay a service invoice) can be combined with eleven real-world complications:
| Complication | Caught by |
|---|---|
| Changed bank details | DESTINATION_NOT_AUTHORIZED |
| Invoice altered after signing | TERMS_MODIFIED |
| Agents signing different totals | TERMS_MISMATCH |
| Forged authority | DELEGATION_FORGED |
| Lookalike supplier | Held, then COUNTERPARTY_NOT_APPROVED |
| Prompt injection | CATEGORY_NOT_PERMITTED |
| Agent revoked mid-payment | LIVE_RECHECK_FAILED, before PayPal is called |
| Duplicate payment | PERMIT_ALREADY_USED |
| Status service outage | Held, then allowed on recheck |
| Wrong item, still authorized | Allowed: an honest limit |
Above the buyer's $250 line, the run genuinely waits until a manager approves the exact terms.
How we built it
- Server: Node 24, Hono and SQLite, with Zod validating every input.
- Identity and delegation:
did:keyidentities. Delegations are credentials shaped like W3C VC 2.0, signed with EdDSA and bound to the agent's key throughcnf. Receipts, permits and a signed evidence bundle can be verified against our public JWKS. - The terms match: terms are canonicalized, then both agents must sign the same digest:
$$h = \mathrm{SHA256}\big(\mathrm{canon}(\text{terms})\big), \qquad \text{match} \iff h_{\text{buyer}} = h_{\text{seller}} = h_{\text{transaction}}$$
Change a single cent after signing and $h$ no longer matches.
- The decision: deterministic and ordered by severity:
$$\text{decision} = \begin{cases} \text{DENY} & \text{if any check fails} \ \text{HOLD} & \text{else if any check is pending} \ \text{REVIEW} & \text{else if amount} > \text{review threshold} \ \text{ALLOW} & \text{otherwise} \end{cases}$$
- Agent access: an MCP connector (TypeScript SDK, Streamable HTTP) for Claude, ChatGPT, Gemini, Grok and others, plus a REST API for agents that hold their own keys. The scenario engine drives agents through pluggable drivers, so real agent APIs can replace the built-in test agents.
- Scenarios without scripts: each run builds a fresh, isolated world with new keys and plays through the same public API a real agent uses. Scenarios only set conditions (an account changed, a mandate revoked); they never state an outcome.
- Tests: 64 automated tests, including every request × complication combination, run against a loopback PayPal mock.
- Frontend and hosting: React 19 and Vite, deployed on Railway at proxyproof.artisolv.com.
Challenges
- Time of check vs time of use. Authorization isn't a one-time event: an agent can be revoked between approval and payment. The executor claims the permit atomically, then re-verifies both sides on live state before PayPal is called, and again at capture.
- Keeping decisions and payments separate. An "allowed" decision is not a payment. We kept decision state and payment state in separate fields, and only PayPal's confirmed capture marks a payment as paid.
- Honest demos. It's tempting to script a demo's outcomes. We made the engine unable to look up expected results; the expectations live only in the tests. That way, a regression shows up as a failing test instead of a demo that quietly lies.
- Human approval that is real. The manager-approval step genuinely pauses the run and produces a signature from the buyer organization's key. It isn't a replayed animation.
- Being clear about limits. ProxyProof answers "was this authorized?", not "was this wise?". It can't stop misuse inside a valid mandate or control an agent's other tools, and the demo says so on screen.
What we learned
- Trust in agentic commerce has to be bilateral. Verifying only the buyer leaves the costliest fraud, the redirected payout, completely open.
- The strongest control is the simplest one: the business, not the agent, decides where money may go, and that decision is a signature.
- Fail closed. "Couldn't check" must mean hold, never allow.
- Most real-world attacks map cleanly to a small set of checks once identity, authority, terms and live status are kept separate.
What's next
- An AP2 mandate adapter.
- OAuth for the MCP connector.
- Verification for chains of agents that hire other agents.
- An AuthZEN policy interface.
- Real agents connected through the driver API.
Built With
- api
- context
- hono
- model
- node.js
- orders
- paypal
- protocol
- react
- sqlite
- typescript