Inspiration
AI agents are starting to spend money. A prompt injection or a buggy model can change a payee, raise an amount or quietly add an item. We wanted a small, checkable gate between the agent and PayPal.
What it does
- The agent (NVIDIA Nemotron via Nebius Token Factory) builds a cart from a merchant catalog.
- A person approves the exact cart. The gate hashes the canonical order and issues a single-use permit.
- The agent can pay only with that permit and that exact order. A changed amount, payee or item, or a replay, is blocked before any PayPal request.
- The PayPal order carries the approved hash in
custom_id. Before capture, the gate reads the order back from PayPal and captures only if it still matches. - Every allowed and blocked attempt writes an Ed25519 signed, hash-chained receipt.
verify.pychecks the chain offline with the public key.
How we built it
Python, FastAPI, httpx, PayPal Orders v2 (sandbox), Ed25519 receipts, SQLite permits. Prices come from the catalog, never from the model.
Live sandbox evidence (in the repo, evidence/)
- Order
4CY72468DE151393K: created, four tampering attempts (raised amount, swapped payee, added item, permit replay) blocked with zero PayPal calls, read-back refused capture while unapproved, buyer approved on PayPal, gate re-checked and captured (capture3M2115364G941974K, COMPLETED, 30.50 USD). 11 receipts VALID. - Order
6YN25222V39712547: created with return and cancel URLs, buyer approved on PayPal and landed on the return page (which never captures), gate read the order back, it matched, and the gate captured (capture71V442751H327834G, COMPLETED, 30.50 USD). 3 receipts VALID. - 72 unit tests pass on Python 3.11 and 3.12; CI uses no credentials. All PayPal calls went to the sandbox. No PayPal Live calls. No buyer data is stored in the evidence.
Try it
git clone https://github.com/TheSingulant/ai4-pay-gate-public
cd ai4-pay-gate-public && pip install -r requirements.txt pytest
python -m pytest -q
python scripts/demo_offline.py
python verify.py evidence/live-20261030T115405Z/receipts.jsonl --pubkey="$(cat evidence/live-20261030T115405Z/public_key.txt)"
Challenges
Getting the buyer redirect right (experience_context return_url, cancel_url, PAY_NOW) and keeping the
return page from ever capturing on its own.
What we learned
Binding approval to a hash of the canonical order is simple and catches every tampering case we tried.
What's next
Webhook verification, multi-item partial refunds with receipts, and a hosted demo.
Log in or sign up for Devpost to join the conversation.