Agent payments, held to what your user signed.
Your user sets the bounds once, with a passkey. AgentGuard checks every payment an agent proposes against them before PayPal moves money, and issues a receipt that holds up in a dispute.
WebAuthn / Ed25519 / RFC 3161 timestamps / PayPal Orders v2
- +0.0sAgent proposes $10.80, over your $5 ask-me lineheld
- +1.9sYou approve with your passkey
- +6.3sCaptured on PayPal · 3XR9…751R
The agent proposes. The mandate decides.
Agents never hold PayPal credentials. Every payment goes through three steps, and money only moves after the last one.
Reserve against a signed mandate
A deterministic policy engine checks payee, SKU, category, quantity, per-transaction and total limits against a mandate the user signed with their passkey. Budget is reserved atomically, so parallel agents cannot double-spend it.
propose_payment → reserved | needs_human | rejected
Verify with PayPal, and with the user
AgentGuard authorizes on the user's vaulted wallet, then reads PayPal's own view of amount, currency and payee. Anything above the user's threshold waits for a passkey approval bound to that exact order.
intent: AUTHORIZE · payee + amount re-checked
Capture, or void, and prove it
Captures run only while the mandate is still active, checked again under lock. Mismatches and revocations void instead. Each capture yields a Proof-of-Intent receipt with a hash-chained audit entry.
captured → receipt + RFC 3161 + anchor
Evidence that holds up without trusting us.
The receipt bundles the user's passkey signature, the order PayPal reported, the approval and the capture. The issuer signs it, two independent timestamp authorities stamp it, and the audit log is anchored.
- Verifies offline with the open-source
@agentguard-dev/intent-verifier, or in your browser. - Retired issuer keys stay published, so old receipts keep verifying after rotation.
- Submitted as seller evidence on a live PayPal sandbox dispute, which PayPal accepted for review. No outcome is claimed.
poi_2b2b34d8c4bfa2e098cadba6
SANDBOX- Issuer
- agentguard-orcin.vercel.app
- Mandate
- mdt_f1a6ef71-d690-48be-a567-fe5de328cedf
- Purpose
- Buy API credits; ask me above 5 USD
- PayPal order
- 8FF97705JB819194D
- Authorization
- 1GV57061K52536844
- Capture
- 3XR94817KX253751R · COMPLETED
- Amount
- 10.80 USD
- Approval
- apr_01139ea1… · passkey
- Timestamps
- freetsa.org · sectigo
- Anchor root
- dc2022104f8eb5f3be03a833d114845820367f4bf3021762437d8987e0d07d57
AP2 describes intent. AgentGuard enforces it on PayPal.
Every receipt carries an ap2 block: the same purchase as Agent Payments Protocol mandates, using AP2's schemas pinned to commit e1ea56d. The verifier re-derives them from the signed mandate.
| AP2 role | AP2 object | Built from | Fidelity |
|---|---|---|---|
| Intent | open_checkout_mandateopen_payment_mandate | Passkey-signed mandate: payees, SKUs, limits, window | exact categories and blocklists stay AgentGuard-only |
| Cart | checkout_mandate | The merchant-created PayPal order AgentGuard read and verified | shape exact checkout_jwt is unsigned (alg none) |
| Payment | payment_mandate | Decision plus PayPal authorization | exact vault token never included |
| User authority | user_authorization | WebAuthn assertion over sha256 of the AgentGuard mandate | differs not SD-JWT; same authority, different signed bytes |
What we guarantee, and where it stops.
The server holds no key that can authorize spending. AI risk analysis is advisory: it can escalate a payment to a human, never approve a failing one.
Enforced
- Mandates, revocations and approvals are signed by the user's passkey. The server re-verifies them on every proposal, approval and capture.
- Approvals are single-use and bound to the exact proposal hash, PayPal order, amount and a five-minute nonce.
- Payee allowlists are checked against the payee PayPal reports, not only what the agent claims.
- Idempotency keys and PayPal-Request-Id on every money call. Recovery never captures against an inactive mandate.
- Every decision lands in a hash-chained, append-only audit log, anchored with RFC 3161 timestamps.
Honest limits
- The first passkey enrollment is bootstrapped by whoever holds the admin token. Deliver links over your own authenticated channel.
- Admin recovery is time-locked for 72 hours and announced, so a takeover is visible as
RECOVERYin receipts, not prevented. - An anchor proves history as of its time. Detecting a forked history needs someone outside to keep copies of the anchors.
- A leaked issuer key stays valid for what it signed before retirement. Rotate promptly, together with the admin token.
- AP2 objects are plain JSON claim sets, not SD-JWT, and the cart JWT is unsigned. Merchant authenticity comes from PayPal's API.
Install, run, verify in about five minutes.
Two packages on npm: a zero-dependency receipt verifier for merchants, and a guard for PayPal's Agent Toolkit for agent builders. The gateway runs on Node 22.6+ with a PayPal sandbox app; SQLite locally, Postgres and Vercel for deployment.
# merchants: verify receipts offline (zero dependencies) npm i @agentguard-dev/intent-verifier # agent builders: guard PayPal's Agent Toolkit (AI SDK, OpenAI, MCP) npm i @agentguard-dev/paypal-toolkit @paypal/agent-toolkit
# sandbox credentials only git clone https://github.com/Godwin-web3/agentguard && cd agentguard npm ci && npm run build:web export PAYPAL_CLIENT_ID=… PAYPAL_CLIENT_SECRET=… export AGENTGUARD_ADMIN_TOKEN=$(openssl rand -hex 24) npm start # http://localhost:8787/console
curl -X POST localhost:8787/v1/agent/payments \ -H "authorization: Bearer $AGENT_KEY" \ -H "content-type: application/json" \ -d '{"agent_id":"agt_…","mandate_id":"mdt_…", "paypal_order_id":"8FF9…","idempotency_key":"order-8110"}' # → { "decision": "needs_human", "approval_id": "apr_…" }
curl https://agentguard-orcin.vercel.app/.well-known/agentguard-keys \ > keys.json # pin this npx -p @agentguard-dev/intent-verifier intent-verify receipt.json --issuer-keys keys.json # VALID receipt poi_… then one ✓ line per check