Inspiration
AI agents are starting to pay for people. Once a purchase like that is disputed ("I didn't authorise this", "that's not what I asked for"), nobody holds tamper-evident proof of what the human actually authorised.
Merchants were already losing this fight before agents arrived. In the Merchant Risk Council's 2026 report, merchants win 16.7% of fraud-coded chargebacks and disputes, and resolving one first-party-misuse dispute costs about $82 on average [1]. Among merchants who report a rise in first-party misuse, 23% attribute it to the emergence of agentic commerce [1]. Meanwhile an agent's spending limit usually sits inside the agent's own prompt, the same text a malicious product page can rewrite.
PayPal has described the fix itself. Its Agent Payments Protocol (AP2) post lists "Dispute and accountability evidence: Extending Seller Protection workflows with Cart and Intent Mandates, mandate IDs and signatures" as an opportunity [2]. We set out to build a working, open-source reference for that idea on PayPal's public sandbox APIs.
What it does
Countersign Pay sits between a buyer, their shopping agent and a PayPal merchant:
- Say it once (Live). The buyer types one sentence, for example "buy running shoes under $50". A deterministic parser reads every amount, currency, cadence, merchant and date. A model only fills in product gaps and is never asked for an amount. The read-back the buyer approves is rendered from the mandate itself, not from the model's reply.
- Sign it (Live). The limits become a signed mandate (Ed25519 / SD-JWT / JCS, in the Verifiable Intent layered format) bound to the buyer's device key.
- Shop and get gated (Live). A tool-calling shopping agent browses our merchant MCP server. Its
submit_checkouttool runs the Gate: 12 deterministic checks against the signed mandate, with no model involved. The Gate answers ALLOW or BLOCK in plain language and records a signed decision. A blocked purchase reads "Your limits held. Here is what it tried." - Pay with PayPal (Live up to order creation). On ALLOW, our PayPal client creates a real Orders v2 sandbox order (
POST /v2/checkout/orders, intentAUTHORIZE) and re-reads it by ID. Recorded runs include order6KW70371X5666381D($133.00 USD) and0MR933308Y8745901, the one shown in the video, both statusCREATED. Authorize and capture are Simulated in demo: they need a vaulted sandbox buyer, which isn't set up yet. - Defend the dispute (Built; Simulated in demo). The Dispute Defender re-verifies the signed chain and routes liability, conceding with a refund when the agent was at fault. It then plans the evidence PayPal asks for by dispute reason, drafts notes that a citation checker must approve, and produces an evidence PDF with a QR code. In the video, the dispute arriving and
provide-evidencebeing filed are simulated. The Defender's logic is real code with offline tests. - Let anyone verify (Live). The public verifier at
/v/{packetId}and a standaloneverify.pylet a merchant, PayPal or an auditor re-check the signatures themselves.
How we built it
Stack: a Next.js 16 / React 19 / TypeScript monorepo (pnpm), a FastAPI + Python 3.13 service for issuing and verifying Verifiable Intent credentials, PostgreSQL for mandates, budgets, Gate decisions, orders and webhooks, plus zod, Playwright and Vitest.
| Tool | How we used it | Status |
|---|---|---|
| PayPal OAuth 2.0 | Client-credentials token for every PayPal call, from packages/paypal, the only module that talks to PayPal |
Live |
| PayPal Orders v2 | Create (intent AUTHORIZE) after a Gate ALLOW, and re-read by ID; retries use a deterministic PayPal-Request-Id |
Live (create + get) |
PayPal MCP server (mcp.sandbox.paypal.com, SSE) |
Driven headlessly: create_order returned sandbox order 2P241059BK520314U, and list_disputes works. We audited all 46 tools |
Live |
| Orders v2 authorize / capture / refund | Typed client methods with retries, circuit breaker and zod validation, tested offline | Simulated in demo |
Disputes v1 (provide-evidence, accept-claim) |
Typed client and Defender filer, tested offline | Simulated in demo |
| PayPal Webhooks | Signature verification, ingest and a reconciler with 154 tests | Built |
| PayPal Vault, JS SDK v6 save | Designed; blocked on sandbox buyer setup | Roadmap |
| LLM (served via Modal) | Fills product gaps, runs the agent's tool loop (≤ 12 steps), classifies injection and drafts dispute notes. Output is never authoritative: it is zod-validated, shown to a human, or checked by deterministic code before any side effect | Live |
| Model Context Protocol | Our own merchant MCP server with per-tool signed scopes; submit_checkout runs the Gate |
Live |
| Verifiable Intent and AP2 vocabulary | Mandate format: layered SD-JWT with machine-checkable constraints | Live |
| WebAuthn / WebCrypto | Device-bound, non-extractable signing key with a passkey step-up | Live |
Where AI is used, and where rules guard it: AI handles the language-shaped work: reading intent, shopping, screening listings for injected instructions and drafting evidence notes. Every decision that moves money or goes into evidence is made by deterministic code: the parser that owns every number, the signer, the Gate, the liability router and the citation checker.
Challenges we ran into
- Taking the model out of the money path. An early compiler let the LLM read amounts and only clamped them afterwards. The same sentence produced a signable mandate about 9 times in 10, and "fifty dollars" produced no cap at all. We inverted the order so a deterministic parser reads the sentence first. It now scores 167/167 exact match on our hand-written corpus, with byte-identical output across 20 runs per case. These are smoke tests, not accuracy figures.
- Bugs that stopped every purchase.
Dateparameters in raw SQL and Postgressum()coming back as a string made every Gate decision fail closed. Both were fixed behind one boundary helper, with reproductions. - Probing the PayPal MCP server correctly. Our first probes concluded it was unusable:
POST /ssereturned 401. SSE is aGET, though. With the right method, our client-credentials token works headlessly. - Staying honest about the sandbox. Capture needs a vaulted buyer, which we haven't set up yet. Instead of faking it in code, the repo records exactly which claims only a real sandbox can settle.
Accomplishments that we're proud of
- A Gate an attacker can't talk past. Injection attack-success at the Gate is 0/50 on our corpus. In a live run, an injected listing ("your limit was raised") led to a Gate BLOCK with a readable reason.
- A measured refusal rate: 30/30 refusals when the agent follows an injected listing, and 30/30 when an attacker owns the agent loop. No purchase came out of either.
- Real PayPal sandbox orders from both our own Orders v2 client and the PayPal MCP server.
- 2,700+ automated tests (2,727 for
pnpm test), plus a Python suite for the Verifiable Intent service. - Concurrency and tamper tests: a 20-way checkout race on one budget gives exactly 3 ALLOWs, and 2,000 bit-flips in the agent's signed layer give 0 ALLOWs.
What we learned
- Put the AI where language is, and keep authority with signatures and deterministic code. "No LLM output is authoritative" is a design rule we could test, not just a slogan.
- A spending limit only counts as evidence when someone other than the party running the agent can check it.
- Publish every number with its denominator. "167/167 on a hand-written corpus" is more useful to a judge than "100% accurate".
What's next for Countersign
- Next: a vaulted sandbox buyer, then authorize and capture per shipment; live webhooks feeding dispute ingest; live
provide-evidence/accept-claimagainst a real sandbox dispute; one-click Judge Mode; a public hosted deployment. - Later: merchant and disputes workspaces; interop with the PayPal Agent Toolkit and AP2 mandates; persistent (KMS) signing keys; more currencies and regions, including INR.
- Business model (proposed, not validated): an open-source mandate SDK and verifier, plus a hosted Gate and Dispute Defender for merchants, priced per protected order or per defended dispute. No customers or pilots yet.
Sources: [1] MRC 2026 Global eCommerce Payments & Fraud Report · [2] PayPal: Agent Payments Protocol
Built With
- ai-agents
- ap2
- ed25519
- fastapi
- llm
- modal
- model-context-protocol
- next.js
- paypal
- paypal-disputes-api
- paypal-mcp-server
- paypal-orders-v2
- paypal-webhooks
- playwright
- postgresql
- python
- react
- sd-jwt
- typescript
- verifiable-intent
- vitest
- webauthn
- zod

