@ava-pay/agent
v0.4.1
Published
Developer preview. Agent SDK for AVA Pay. Sign Visa Trusted Agent Protocol (RFC 9421), Google Agent Payments Protocol (AP2), and IETF Web Bot Auth requests. Plus the parsing primitives for building your own verifier. Pre 1.0: API may change.
Maintainers
Readme
@ava-pay/agent
Status: developer preview (0.4.x). The API surface may change before 1.0. 0.4.0 is the floor for anyone verifying Web Bot Auth requests whose key directory serves Appendix B proofs: 0.3.x classifies chatgpt.com's directory proof
invalid, so a verifier built on it rejects every ChatGPT-signed request (key_proof_invalid). That failure was closed; nothing that should have failed was accepted. 0.4.0 is additive at the type level. Direct callers ofverifyDirectoryProofsmust now pass the response'sContent-Digestheader, or an offered proof isinvalid.(0.3.0 carried three security fixes to the Web Bot Auth and RFC 9421 parsers, two of which let requests that must never verify come back
trusted: true. Do not run anything older.) See CHANGELOG.md.(0.2.0 was a breaking release: the AP2 v0.1 Intent/Cart API
buildAp2Headers,signIntentMandate,signCartMandatewas removed in favor of AP2 v0.2 dSD-JWT mandate chains. See the AP2 section below.)Be clear about what each protocol's verdict means. Web Bot Auth and browse-intent Visa TAP prove agent identity ("this request really came from this agent operator"), not that an end user authorized a purchase. The AVA TAP-style profile's mandate is agent-signed in this preview. AP2 v0.2 chains carry a user-rooted delegation (the root mandate is signed by the user/wallet key and delegates to the agent's key), which is the closest thing here to end-user payment authorization.
Sign AI-agent requests for all four protocols the AVA Pay merchant-side verifier accepts, from Node.js (≥20, zero dependencies):
- Visa Trusted Agent Protocol (real wire format:
agent-browser-auth/agent-payer-authtags) - IETF Web Bot Auth (the scheme real ChatGPT agent traffic uses), tracking
draft-ietf-webbotauth-httpsig-protocol-00(formerlydraft-meunier-webbotauth-httpsig-protocol-02; adopted 2026-09-01, content-identical, section numbers unchanged) - Google Agent Payments Protocol v0.2 (dSD-JWT Checkout / Payment mandate chains)
- AVA's TAP-style profile (RFC 9421 + Ed25519 +
x-ava-mandate)
Plus the parsing/verification primitives to build your own merchant-side verifier.
Install
npm install @ava-pay/agentUse
Visa Trusted Agent Protocol (real wire format)
import { generateAgentKeyPair, signWithVisaTap } from '@ava-pay/agent';
const { privateKey } = generateAgentKeyPair(); // Ed25519; RSA keys work too (rsa-pss-sha256)
const signed = signWithVisaTap({
url: 'https://shop.example.com/products/tool-1234',
privateKey,
keyid: 'agent_acme_shopping', // resolvable via the merchant's directory
tag: 'agent-browser-auth', // or 'agent-payer-auth' at checkout
});
await fetch(signed.url, { method: signed.method, headers: signed.headers });Checkout requests can attach the signed body objects (agenticConsumer /
agenticPaymentContainer) via signTapObject: kid/alg/nonce must match the
message signature.
Web Bot Auth (IETF)
import { generateAgentKeyPair, signWithWebBotAuth, webBotAuthKeyId } from '@ava-pay/agent';
const { publicKey, privateKey } = generateAgentKeyPair();
// Publish publicKey (as a JWK) at
// https://YOUR-ORIGIN/.well-known/http-message-signatures-directory
// Verifiers resolve it by RFC 7638 thumbprint: webBotAuthKeyId(publicKey).
const signed = signWithWebBotAuth({
method: 'GET',
url: 'https://shop.example.com/products/tool-1234',
signatureAgent: 'https://your-agent.example',
privateKey,
});By default this emits the bare-string Signature-Agent form that deployed
agents send today. Section 5.2.1 of the draft requires signers to send the
dictionary form and to cover the member keyed to their own signature label:
const signed = signWithWebBotAuth({
method: 'GET',
url: 'https://shop.example.com/products/tool-1234',
signatureAgent: 'https://your-agent.example',
privateKey,
signatureAgentFormat: 'dictionary', // Signature-Agent: sig1="https://your-agent.example"
// keyedSignatureAgentComponent defaults to true here, covering
// "signature-agent";key="sig1" rather than the whole field.
// signatureAgentType: 'jwks_uri', // optional §5.5 ;type= parameter
});Verifiers accept both forms. The draft's Appendix E.2.1 vector verifies against this signer end to end.
Google AP2 v0.2 (mandate chains)
An AP2 v0.2 presentation is a delegated SD-JWT chain: a user-signed open
mandate (constraints + the agent's key as cnf) followed by an
agent-signed closed mandate committing to a merchant-signed checkout.
import {
generateAgentKeyPair,
buildCheckoutMandateChain,
buildPaymentMandateChain,
computeCheckoutHash,
} from '@ava-pay/agent';
const user = generateAgentKeyPair(); // the wallet/root key, registered in a directory
const agent = generateAgentKeyPair(); // the agent's key, delegated via cnf
const checkoutChain = buildCheckoutMandateChain({
user: { privateKey: user.privateKey, kid: 'user_wallet_1' },
agentPrivateKey: agent.privateKey,
agentPublicKey: agent.publicKey,
constraints: [
{ type: 'checkout.allowed_merchants', allowed: [{ name: 'Shop', url: 'https://shop.example.com' }] },
],
checkoutJwt, // merchant-signed UCP Checkout JWT
aud: 'https://shop.example.com', // the merchant origin
nonce: crypto.randomUUID(), // single-use; verifiers deduplicate
});
await fetch('https://shop.example.com/cart', {
method: 'POST',
headers: {
'ap2-checkout-mandate': checkoutChain,
// optional: 'ap2-payment-mandate': buildPaymentMandateChain({ ... transaction_id: computeCheckoutHash(checkoutJwt) ... }),
},
});(The Ap2-Checkout-Mandate / Ap2-Payment-Mandate headers are AVA Pay's
HTTP binding of AP2 v0.2; AP2 itself specifies A2A message transport.)
AVA TAP-style profile
import { generateAgentKeyPair, signWithVisa } from '@ava-pay/agent';
const { privateKey } = generateAgentKeyPair();
const signed = signWithVisa({
method: 'POST',
url: 'https://shop.example.com/cart',
body: JSON.stringify({ items: [{ sku: 'TOOL-1234', qty: 1, price: 4999 }] }),
agentId: 'agent_acme_shopping',
privateKey,
mandate: {
id: `mandate_${Date.now()}`,
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 600,
maxAmountMinor: 50_000,
currency: 'USD',
allowedMerchants: ['shop.example.com'],
buyer: { buyerId: 'user_abc' },
},
});Build your own verifier
import { parseSignatureInput, buildSignatureBase, verifyEd25519 } from '@ava-pay/agent/protocol/visa';
import { parseSignatureAgent, parseKeyDirectory, ed25519JwkThumbprint } from '@ava-pay/agent/protocol/web-bot-auth';
import { verifyTapSignature, parseVisaJwks, parseIdToken } from '@ava-pay/agent/protocol/visa-tap';
import { verifyChain, checkCheckoutConstraints } from '@ava-pay/agent/protocol/ap2';API surface
Signers
generateAgentKeyPair(): AgentKeyPair: fresh Ed25519 keypairsignWithVisaTap(input): SignedRequest/signTapObject(fields, key): real Visa TAPsignWithWebBotAuth(input): SignedRequest/webBotAuthKeyId(key): string: Web Bot AuthcreateRootMandate/presentMandate/buildCheckoutMandateChain/buildPaymentMandateChain/makeCheckoutJwt/computeCheckoutHash: AP2 v0.2 chainssignWithVisa(input): SignedRequest/encodeMandate(m): AVA TAP-style profile
Types
Mandate,BuyerInfo,IncomingRequest,VerificationResult,VerificationFailureReasonVerifiedProtocol,VerifiedAgentIdentity,TapVerificationDetail,OperatorRecordVerificationResultcarries an optionalconclusiveflag on both branches. Each failure reason's value is fixed by the exportedREASON_CONCLUSIVEtable, which is the list of reasons and their outcomes:falsemeans the verifier could not complete its checks (the reasons are also exported asCOULD_NOT_CHECK_REASONS),truemeans the request was definitively rejected.trustedstays false either way, so fail-closed behavior never depends on the flag. Read an absent value as conclusive.rejection(reason, message)builds a failure result whose flag comes from the table.VerifiedAgentIdentity.bindingis'domain'for a key found through the reserved well-known directory path and'url-only'for one declared viajwks_uri/cimd, which proves key continuity at a URL with no origin association.CheckoutMandate,OpenCheckoutMandate,PaymentMandate,OpenPaymentMandate,CheckoutCheckoutConstraintEvaluator,PaymentConstraintEvaluator(pluggable validator registries)
Subpath imports for low-level work
@ava-pay/agent/protocol/visa: RFC 9421 parser, signature base, Ed25519 verify, content-digest@ava-pay/agent/protocol/visa-tap: TAP tags/algorithms, Visa JWKS + PS256 IdToken parsing, signed body objects@ava-pay/agent/protocol/web-bot-auth: Signature-Agent parsing (both wire forms, §5.5 discovery types), RFC 7638 thumbprints, key-directory parsing, Appendix B directory proof-of-possession (verifyDirectoryProofs/signDirectoryResponse)@ava-pay/agent/protocol/ap2: dSD-JWT chain verify, v0.2 mandate shapes, constraint evaluators, compact-JWS helpers
Onboarding
To get a key recognized by AVA Pay merchants, either register it with the AVA Agent Directory (see AGENT_ISSUERS.md) or publish it at your origin's Web Bot Auth key directory. AVA Pay merchants resolve keys through a federated chain (Visa's directories → Web Bot Auth agent cards → the AVA directory), so a key published once works across protocols.
License
MIT
