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

FileRole
src/tap.tsVisa Trusted Agent Protocol — key gen, sign, verify
src/server.tsMerchant server — both card and Tempo endpoints
src/agent-card.tsAI agent using Visa card-based MPP
src/agent-tempo.tsAI 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:

  1. Signature-Input / Signature headers — RFC 9421 Ed25519 message signature. The merchant reconstructs the signature_base string and verifies against the agent's public key (fetched from mcp.visa.com/.well-known/jwks in production).

  2. X-Agent-Consumer header — Agentic Consumer Recognition Object. Contains idToken (Visa-signed JWT), device/location contextualData, and a signature tied to the same nonce as the message signature.

  3. 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() with visaAcceptanceClientEnabler({ ... })
  • 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: false and use mainnet currency/recipient addresses
  • Consider pull mode + feePayer to let the server sponsor gas
  • Add waitForConfirmation: false for latency-sensitive routes

TAP (both paths)

  • Remove the /tap/register-key endpoint — 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 idToken JWT signature using Visa's public key

Key differences: Card vs Tempo

CardTempo
CredentialJWE-encrypted network tokenSigned blockchain transaction
SettlementT+1 / T+2 fiat< 1 second USDC
CurrencyFiat (USD, EUR, ...)USDC / pathUSD
IssuerVisa Intelligent CommerceAgent's own wallet
ReachAll card-accepting merchantsMPP-enabled services
GasNoneUSDC (or sponsored)
Best forMax merchant reach todayMicropayments, M2M, programmable

Both paths share the same TAP identity layer and MPP protocol envelope — only the credential format and settlement rail differ.

References