MPP + Visa TAP
March 20, 2026 · View on GitHub
A minimal end-to-end demonstration of autonomous AI agent payments using:
- Machine Payments Protocol (MPP) — open standard by Stripe + Tempo
- Visa Trusted Agent Protocol (TAP) — cryptographic agent identity (RFC 9421)
- mpp-card — Visa card-based MPP (network tokens, existing card rails)
- mppx — Tempo stablecoin MPP (USDC, Tempo L2 blockchain)
Architecture
Three stacked layers, shared by both payment paths:
┌─────────────────────────────────────────────────────────┐
│ Visa Trusted Agent Protocol (identity) │
│ Agent signature · Consumer object · Payment container │
│ Ed25519 keys · RFC 9421 · JWKS endpoint │
└──────────────────────┬──────────────────────────────────┘
│ HTTP headers on every request
┌──────────────────────▼──────────────────────────────────┐
│ Machine Payments Protocol (MPP) │
│ HTTP 402 Challenge / Authorization: Payment │
│ ┌─────────────────┐ ┌───────────────────────┐ │
│ │ Card (mpp-card)│ │ Tempo (mppx) │ │
│ │ JWE token │ │ Signed USDC tx │ │
│ └────────┬────────┘ └──────────┬────────────┘ │
└────────────┼────────────────────────────┼───────────────┘
│ │
┌────────▼────────┐ ┌─────────▼──────────┐
│ Visa card rails│ │ Tempo L2 chain │
│ Fiat, T+1/T+2 │ │ USDC, <1s final. │
└─────────────────┘ └────────────────────┘
Files
| File | Role |
|---|---|
src/tap.ts | Visa Trusted Agent Protocol — key gen, sign, verify |
src/server.ts | Merchant server — both card and Tempo endpoints |
src/agent-card.ts | AI agent using Visa card-based MPP |
src/agent-tempo.ts | AI agent using Tempo USDC stablecoin MPP |
Quick start
npm install
# Terminal 1 — start merchant server
npm run server
# Terminal 2 — run card agent
npm run agent:card
# Terminal 3 — run Tempo agent
npm run agent:tempo
How it works
TAP message signature (every request)
Every agent request carries three linked objects sharing a single nonce:
-
Signature-Input / Signature headers — RFC 9421 Ed25519 message signature. The merchant reconstructs the
signature_basestring and verifies against the agent's public key (fetched frommcp.visa.com/.well-known/jwksin production). -
X-Agent-Consumer header — Agentic Consumer Recognition Object. Contains
idToken(Visa-signed JWT), device/locationcontextualData, and a signature tied to the same nonce as the message signature. -
X-Agent-Payment header — Agentic Payment Container. Contains the payment credential or hash, tied to the same nonce.
The nonce links all three objects together — if any is tampered with or replayed, the nonce check fails.
Card payment flow
Agent Merchant Credential Issuer
│ │ │
│─── GET /resource/card ──▶ │
│ (TAP headers) │ │
│ ◀─── 402 Challenge ───────────│
│ │ amount, currency, │
│ │ RSA public key │
│ │ │
│─────────────────────────│──── cardId + challenge ▶
│ │ JWE token
│ │ (RSA-OAEP-256 encrypted)
│ │ │
│─── GET /resource/card ──▶ │
│ Authorization: │ │
│ Payment <JWE> │ │
│ │── decrypt ──▶ gateway │
│ │ charge │
│◀── 200 + Payment-Receipt│ │
The JWE is encrypted with the merchant's RSA-2048 public key (from the 402 Challenge), so only the merchant can decrypt the network token. The token is single-use and time-bounded.
In production, replace mockClientEnabler() with visaAcceptanceClientEnabler()
from Visa Intelligent Commerce, and mockServerEnabler() with
visaAcceptanceServerEnabler() wired to your payment gateway.
Tempo payment flow
Agent Merchant Tempo L2
│ │ │
│─── GET /resource/tempo ─▶ │
│ (TAP headers) │ │
│ ◀─── 402 Challenge │
│ │ 0.01 USDC, │
│ │ Tempo chain params │
│ │ │
│── build USDC tx ────────│ │
│ sign with agent key │ │
│ broadcast to chain ───│────────────────────▶
│ │ tx confirmed │
│─── GET /resource/tempo ─▶ │
│ Authorization: │ │
│ Payment <tx hash> │ │
│ │── verify on-chain ─▶
│◀── 200 + Payment-Receipt│ │
In push mode (default for local accounts), the agent broadcasts the transaction
itself and sends the hash. In pull mode, the agent sends the signed serialized
transaction and the server broadcasts (enabling gas sponsorship via feePayer).
Production checklist
Card path
- Onboard to Visa Intelligent Commerce
- Replace
mockClientEnabler()withvisaAcceptanceClientEnabler({ ... }) - Replace
mockServerEnabler()with your gateway (Stripe, Adyen, etc.) - Store RSA private key in a secrets manager (not env file)
- Point TAP JWKS to
https://mcp.visa.com/.well-known/jwks(remove /tap/register-key endpoint)
Tempo path
- Fund agent wallet with USDC/pathUSD on Tempo mainnet
- Set
testnet: falseand use mainnet currency/recipient addresses - Consider
pullmode +feePayerto let the server sponsor gas - Add
waitForConfirmation: falsefor latency-sensitive routes
TAP (both paths)
- Remove the
/tap/register-keyendpoint — keys come from Visa JWKS in production - Cache public keys with TTL (spec allows up to 8-minute window)
- Implement nonce replay detection (store last 8 minutes of nonces)
- Validate
idTokenJWT signature using Visa's public key
Key differences: Card vs Tempo
| Card | Tempo | |
|---|---|---|
| Credential | JWE-encrypted network token | Signed blockchain transaction |
| Settlement | T+1 / T+2 fiat | < 1 second USDC |
| Currency | Fiat (USD, EUR, ...) | USDC / pathUSD |
| Issuer | Visa Intelligent Commerce | Agent's own wallet |
| Reach | All card-accepting merchants | MPP-enabled services |
| Gas | None | USDC (or sponsored) |
| Best for | Max merchant reach today | Micropayments, M2M, programmable |
Both paths share the same TAP identity layer and MPP protocol envelope — only the credential format and settlement rail differ.