Inspiration

AI agents can now browse shops, fill carts and even call PayPal's own tools. But giving one a payment method is all or nothing: one poisoned product page, a wrong variant or a sneaky add-on, and it spends money you never meant to spend. Then the second problem starts: when the buyer disputes, nobody can prove what they actually authorised. Not the buyer, not the seller, not PayPal. We wanted both halves fixed: agents that can only spend what you approved, and proof that holds up in a dispute.

What it does

Buyer side: the agent proposes, you countersign.

  • A signed permission, not your wallet. You tell the agent what to buy, the budget, which shops and until when, and approve it with your fingerprint (passkey / Touch ID).
  • One door to pay. The agent can only pay through the Countersign gateway. The gateway prices the cart itself and runs plain-code rules (budget, quantity, allowed shops, expiry). AI checks then ask two questions: does the cart match what you asked for, and did the shop's page try to give the agent orders?
  • Good carts pass, doubtful ones wait, bad ones stop. A held cart shows every line, why it was held and the AI's own words, and you approve that exact cart. A blocked cart shows a clear "Stopped" panel: your shops vs the shop it tried, your budget vs this cart, or the hidden text on the page and the gift card it slipped in.
  • Works on ordinary websites. A checkout guard drives Chrome for the agent. You watch it shop, with a banner and each click highlighted. Payment shows " Payment locked" until you approve the exact cart it read from the shop, then unlocks by itself, and relocks if the cart changes.
  • Guards PayPal's own agent tools. Countersign sits in front of PayPal's official MCP server. Read-only tools pass through. A refund, invoice or new product waits in your Inbox in plain words ("Give a customer 25 USD back", "Money moves if you say yes"), then runs once, exactly as approved.
  • A fleet of agents, each in its own lane: separate keys, 30-day caps, and one-click "stop all agents".

Seller side: the receipt becomes evidence.

  • Every purchase gets a signed receipt in a tamper-evident log, witnessed by a public timestamp authority.
  • The Dispute Desk imports PayPal disputes and verifies the buyer's receipt on its own, without trusting the buyer's gateway. It builds the evidence packet and suggests fight or refund; staff choose, and the answer goes back to PayPal.

Judges can try it in 60 seconds: the live site's judge mode lets a demo agent try six carts while you decide.

How we built it

  • Gateway: Python, FastAPI, MCP (the agent's only door), PostgreSQL. PayPal Orders v2 and Disputes APIs in the sandbox.
  • AI checks: GroqCloud openai/gpt-oss-20b for typed JSON cart matching, and meta-llama/llama-prompt-guard-2-86m for injected instructions. If a check fails or times out, the cart is held, never approved.
  • Trust:
    • Ed25519-signed permissions and receipts.
    • WebAuthn passkey signatures over exactly what you saw.
    • A hash-chained audit log witnessed by RFC 3161 timestamp authorities (DigiCert, FreeTSA).
    • Shop-signed cart offers.
    • A focused subset of AP2 v0.2 Checkout and Payment Mandates.
  • Checkout guard: Playwright driving Chrome, exposed to the agent as an MCP server. Its banner lives in a closed shadow root, so the agent can't read or fake it.
  • PayPal guard: an MCP proxy in front of PayPal's official MCP server (@paypal/mcp, Agent Toolkit). Each approval is bound to a hash of the exact arguments.
  • Agent: Claude, through Claude Code and the Claude Agent SDK.
  • Web app: React + TypeScript (buyer Inbox, approvals, audit log, receipts; seller Dispute Desk).
  • Hosting: Render, with one Blueprint for the gateway, Dispute Desk, demo shops, web app and PostgreSQL.
  • Evaluation: 167 labelled carts, 55 held out, with every model reply stored word for word.

Challenges we ran into

  • Our agent was too good. A real Claude agent refused our poisoned product page, so the "fooled agent" scene had to be scripted. The gateway still has to stop a fooled agent, and it does.
  • PayPal vault tokens belong to one merchant, so a third-party gateway can't pay at any shop. We made PayPal's own checkout the payment step and Countersign the gatekeeper in front of it.
  • Same question, different answers. A language model can answer the same cart differently twice. We store every reply and measure on a held-out set instead of trusting one good run.
  • Making safety visible. A block in a log means nothing to a normal user. We redesigned every "no" so it shows what you allowed vs what the agent tried, in plain words.

Accomplishments that we're proud of

  • 0 of 114 carts that should have been stopped were paid without asking (95% CI 0–3%).
  • All 15 held-out hidden-instruction attacks were caught.
  • Pack sizes are counted in code: 13 of 13 new held-out pack cases right.
  • Real sandbox disputes answered from the desk: a refund offer, and evidence accepted.
  • Receipts anyone can check with public keys only: the gateway's signature, the buyer's device signature, the shop's signature and the outside timestamp.
  • Approvals for PayPal's own agent tools that an everyday user can understand, and that run exactly once.

What we learned

  • The model is the weakest link, so it never has the last word. Money rules are plain code, and doubt means "ask the person".
  • Proof is worth more when the other side can check it. The seller verifies the buyer's receipt on its own, without trusting the buyer's gateway.
  • Consent must be specific. "This exact cart, once" is easy to give and easy to prove. "My card, for anything" is neither.

What's next for Countersign

  • Inside PayPal as a wallet feature, so saved consent is released only for countersigned carts, at any merchant.
  • Structured agent checkouts (e.g. Shopify) instead of reading pages, plus more shops.
  • Better-calibrated AI checks, and letting you edit a permission (budget, shops) before approving.
  • Receipts as a standard, so any seller or PayPal itself can check an agent purchase in a dispute.

Built With

Share this project:

Updates

Submission history