-
-
Signed mandates with the verbatim instruction, exact cart, cap and verification checks, mapped to AP2 fields.
-
Analytics (AG Charts): agent vs other disputes, proposals by status, and who is liable.
-
Live Gemini investigation: dispute, tracking, signed mandate and playbook, then the intent judgement.
-
Merchant dispute inbox (AG Grid) with due-date colours and which disputes came from AI agents.
-
Live storefront: Gemini picks the item, signs a mandate with the user’s exact words, and creates a PayPal order.
-
Approval-gated: nothing reaches PayPal until the merchant approves.
-
Legitimate purchase: the mandate verifies and matches, so Vouch contests with mandate evidence and proof of delivery.
-
Honest verdict: accept the socks-for-shoes dispute and recover from the agent platform (win 10%, EV -$2.40).
-
Scenario 1: the agent bought exactly what was asked. Intent matches, the buyer is liable, and Vouch proposes to contest (90% win).
-
Scenario 2: socks for a shoes request. The gate flags it, the agent platform is liable, and Vouch honestly recommends accepting.
Inspiration
AI shopping agents have started paying for things through PayPal on people's behalf. Picture this: you tell an agent to "buy me running shoes under \$100." A week later a pair of socks shows up, and you file a dispute saying "I never authorised this."
Who is actually at fault? You did authorise something. The merchant shipped what was ordered. The agent misread what you wanted. Today's dispute flow can't tell these cases apart, because nothing in a PayPal dispute connects back to what the human delegated to the agent. Tools for automating chargebacks already exist, but they all assume a human clicked "buy." I wanted to build the missing link: a layer that knows about mandates and can turn "what did the person ask for?" into evidence that holds up in a dispute.
What it does
Vouch is a bridge from mandates to disputes for PayPal agentic commerce:
- Signed mandates. The shopping agent signs an Intent mandate and a Cart mandate (ed25519). The intent records the user's exact words, the agent's interpretation, a spending cap, the allowed merchants and an expiry time. The cart is tied to the intent by its hash.
- Prevention. A check before fulfillment returns
ship,revieworholdfor agent orders, so they don't turn into disputes later. - Investigation. When a dispute arrives, an agent gathers the dispute, the tracking and the mandate using read-only tools, verifies the signatures, and judges intent fidelity: does the signed cart match what the user asked for?
- Liability decided in code. Code, not the model, decides who is liable: the buyer, the agent platform or the merchant.
- Honest proposals that need approval. Vouch proposes contesting or accepting the dispute. When the agent platform got it wrong, Vouch tells the merchant to accept and recover the money from the platform. Nothing reaches PayPal until a human approves it.
How I built it
- Stack: TypeScript on Node 20 and Express, with an AG Grid inbox and AG Charts analytics for the merchant console.
- PayPal: Orders v2 for agent checkouts, the Customer Disputes API for evidence and accept-claim, and webhook verification. The PayPal Agent Toolkit over MCP is optional and gives the agent read-only PayPal tools, with a REST fallback.
- Mandates: ed25519 signatures, with a projection onto the fields of AP2 v0.2 Checkout and Payment mandates.
- Models: one tool-use loop shared by Gemini (the free default) and Claude (optional).
The core design choice is a trust boundary: the model is allowed to make exactly one judgement,
$$ \text{intent} \in {\texttt{match},\ \texttt{mismatch},\ \texttt{unclear}} $$
Everything that involves money is deterministic. For a dispute of amount $A$, code estimates a win probability $p$ from the liability verdict and the delivery status, then computes the expected value of contesting:
$$ \mathrm{EV} = p \cdot A - c, \qquad c = \$12 \ \text{(handling cost)} $$
It recommends contesting only when
$$ p \ge 0.5 \ \land \ \mathrm{EV} > 0 $$
and accepting otherwise. For example, an unauthorised-purchase dispute where the buyer is liable and the order was delivered gives $p = 0.1 + 0.65 + 0.15 = 0.9$, which is a clear contest. If the agent platform's cart didn't match the intent, $p$ drops to $0.1$ and Vouch recommends accepting.
Challenges I ran into
- Keeping the LLM away from money decisions while still using it for the one thing it does well: comparing a messy instruction like "comfy running shoes, nothing flashy" with a concrete cart. The fix was to split the work strictly. The model judges intent, and code handles signatures, caps, liability, probabilities and approvals.
- Being honest about liability. A dispute tool's instinct is to always fight. Building one that sometimes says "you'll lose this, accept it and go after the agent platform" meant I had to model the agent platform as its own liable party.
- Demoing without credentials. PayPal sandbox disputes are hard to create on demand. I built a replay mode that loads fixture disputes, simulates orders and sends PayPal write calls to an in-memory recorder. That way
npm run demotells the whole story offline. Running withVOUCH_REPLAY=1uses fixture disputes with a live model. - Free-tier model limits. Gemini's free tier sometimes returns 503 errors under load, so the loop has to fall back to a lighter model without breaking the investigation trace.
- Checking against real services. I confirmed OAuth, listing disputes and creating Orders v2 on a real sandbox, and placed a real order under a signed mandate. A few dispute payloads (dispute detail, evidence, accept-claim, capture) are still marked
UNVERIFIEDin the code until I can trigger a real sandbox dispute.
What I learned
- Delegation is a chain of evidence. When the "buyer" is software, the useful question is no longer "did the cardholder click buy?" It becomes "does what was bought match what was delegated, and can each step be proven?" Signed mandates bound by hashes answer that cheaply.
- The LLM works best as a narrow judge, not a decision-maker. Limiting the model to one ternary output made the system easier to test and easier to explain, and it holds up better against prompt injection.
- Emerging standards like AP2 are worth tracking early. Projecting my mandate format onto AP2 v0.2 fields showed me what's missing, such as per-user keys and SD-JWT. That's where I'm heading next.
What's next
- Confirm the remaining dispute payloads against a real sandbox dispute.
- Move to SD-JWT mandates with per-user keys, as AP2 v0.2 specifies.
- Store data in a database instead of JSON files, and add checks against each merchant's refund policy.
Built With
- ag-charts
- ag-grid
- ap2
- claude
- css
- ed25519
- express.js
- gemini
- html
- javascript
- model-context-protocol
- node.js
- paypal
- paypal-agent-toolkit
- paypal-disputes-api
- paypal-orders-api
- postman
- render
- typescript