Agent protocols — TAP / MPP / ACP
Three open standards. Same Ed25519-signed envelope. Pick the one that fits your use case.
| Protocol | Acronym | Best for | |---|---|---| | Trusted Agent Protocol | TAP | First-party agents (yours) on your workspace — identity attestation | | Machine Payments Protocol | MPP | High-frequency M2M — light envelope, designed for automation | | Agentic Commerce Protocol | ACP | Shopping flows — search → decide → pay |
Wire format
All three share the same PaymentIntent shape. Only the protocol field differs.
type PaymentIntent = {
agent_id: string;
merchant: {
name: string;
mcc: string; // ISO 18245 4-digit
reap_merchant_id?: string;
};
amount: {
currency: string; // ISO 4217 (USD, EUR, GBP, etc.)
cents: number;
};
funding?: {
asset?: "USDC" | "USDT" | "BTC" | "ETH" | "SOL";
chain?: "ethereum" | "polygon" | "arbitrum" | "base" | "optimism" | "solana";
};
intent_id: string; // UUIDv4; unique forever
iat: number; // unix seconds, issued-at
exp: number; // unix seconds, expiry (iat + ttl)
protocol: "TAP" | "MPP" | "ACP";
description?: string;
};Canonical JSON
The signature is computed over canonical JSON: keys sorted alphabetically at every level, no whitespace. Byte-identical between TypeScript and Python SDKs (verified in our test suite).
function canonicalize(obj) {
if (obj === null || typeof obj !== "object") return JSON.stringify(obj);
if (Array.isArray(obj)) return "[" + obj.map(canonicalize).join(",") + "]";
const keys = Object.keys(obj).sort();
return "{" + keys.map(k => JSON.stringify(k) + ":" + canonicalize(obj[k])).join(",") + "}";
}Signed envelope
type SignedIntent = {
intent: PaymentIntent;
signature: string; // base64url-encoded Ed25519 signature over canonical JSON
key_id: string; // base64url(sha256(SPKI-DER of the public key))
};Server-side verification
- Look up the agent by
signed.intent.agent_id - Fetch its registered
publicKey(stored as DER) - Compute
keyFingerprint(publicKey)and compare tosigned.key_id(constant time) - Recompute canonical JSON of
signed.intent - Verify the signature with one-shot
crypto.verify(null, canonical, publicKey, sig) - Check
signed.intent.exp > nowandsigned.intent.iat <= now + 60 - Insert into
agent_intent_logwith the unique-constraint onintent_id— replays return HTTP 422
TTL recommendation
| Protocol | Typical TTL | |---|---| | TAP | 300s (5 min) | | MPP | 60s (1 min) — high-frequency | | ACP | 600s (10 min) — multi-step shopping flow |
Even for retries, generate a fresh UUIDv4. The server uses this for replay protection and will reject duplicates with HTTP 422.
Visa Intelligent Commerce
When our issuing partner accepts a signed intent, it routes through Visa's Intelligent
Commerce network using a Visa-issued credential bound to your agent. The
resulting auth flows through our standard webhook at
/api/webhooks/reap-authorization, where our authorization engine applies
your card scopes (MCC, merchant, limits) before sending the final decision
back to the issuing partner → Visa within ~200ms.