Skip to content
AVA-PAYPublic

About

Merchant-side trust gateway for AI shopping agents. One API verifies Visa Trusted Agent Protocol, Google AP2 v0.2, and IETF Web Bot Auth (real ChatGPT traffic): RFC 9421, Ed25519, SD-JWT. Live on the Shopify App Store.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

AVA Pay

The merchant-side trust gateway for AI shopping agents — one API that verifies any signed agent and tells merchants exactly what that verification means.

One install on the merchant side. One registration on the agent-issuer side. Four protocols supported out of the box — Visa Trusted Agent Protocol (the real wire format: agent-browser-auth/agent-payer-auth tags, Consumer Recognition Objects, Agentic Payment Containers, Visa-signed IdTokens), AVA's TAP-style profile (RFC 9421 + Ed25519 + x-ava-mandate), Google Agent Payments Protocol (AP2 v0.2 dSD-JWT mandate chains), and IETF Web Bot Auth (the signature scheme real ChatGPT agent traffic carries today). One protocol-agnostic VerificationResult shape downstream.

Web Bot Auth results are agent identity only — they prove which agent operator sent the request (e.g. https://chatgpt.com), not that a buyer authorized a purchase. Payment authority comes from the mandate-carrying protocols (Visa TAP, AP2).

If you're an AI agent issuer, the on-ramp is AGENT_ISSUERS.md — publish or register your public key once, and every AVA Pay merchant can verify you on any protocol you sign with.

60-second quickstart

npm install
npm run demo            # AVA TAP-style profile (RFC 9421 + Ed25519 + x-ava-mandate)
npm run demo:ap2        # Google AP2 v0.2 (dSD-JWT mandate chains)
npm run demo:wba        # IETF Web Bot Auth (identity-only, key-directory discovery)
npm run demo:tap        # Visa Trusted Agent Protocol, real wire format

You'll see a signed request and a ✓ trusted response in well under a millisecond, on every protocol. The same primitives the demo uses are published as @ava-pay/agent on npm — that's what production agents import.

Repo layout

AVA Pay/
├── packages/
│   └── agent-sdk/                  # @ava-pay/agent — publishable npm package
│       ├── src/agent/{visa,ap2}.ts # signWithVisa, buildCheckoutMandateChain, …
│       ├── src/protocol/visa/      # RFC 9421 primitives (parser, base, Ed25519 verify)
│       ├── src/protocol/ap2/       # dSD-JWT chains, v0.2 mandates, JWS primitives
│       ├── src/types.ts            # canonical shared types
│       └── src/index.ts            # public exports
├── src/                            # API server (depends on @ava-pay/agent)
│   ├── routes/verify.ts            # POST /verify, schema-validated, structured logs
│   ├── verifier/
│   │   ├── interface.ts            # AgentVerifier — the swap point
│   │   ├── multi.ts                # MultiProtocolVerifier — header-sniffs and dispatches
│   │   ├── visa.ts                 # AVA TAP-style profile verifier (x-ava-mandate)
│   │   ├── visa-tap.ts             # REAL Visa TAP verifier + Visa JWKS resolver
│   │   ├── ap2.ts                  # AP2 verifier
│   │   ├── web-bot-auth.ts         # Web Bot Auth verifier + key-directory resolvers
│   │   ├── agent-directory.ts      # Static/Remote + 5-min CachingAgentDirectory (kid/protocol-aware)
│   │   ├── federated-directory.ts  # federated resolution chain across roots of trust
│   │   ├── mandate.ts              # shared mandate scope check
│   │   └── mock.ts                 # non-crypto mock for plumbing tests
│   ├── directory/
│   │   ├── types.ts                # DirectoryAgentRecord, DirectoryAgentKey
│   │   ├── storage.ts              # InMemory + File backed stores
│   │   ├── routes.ts               # /.well-known + /directory/agents (register, lookup, revoke)
│   │   └── storage-directory.ts    # adapter — verifier resolves through hosted storage
│   └── server.ts                   # buildServer — composes verifier, directory, static landing
├── public/                         # static landing page (live in-browser demo using Web Crypto)
├── examples/agent-demo.ts          # runnable Node demo (all four protocols)
├── tests/                          # 597 Vitest cases — real cryptography, no fakes
├── scripts/check-type-sync.ts      # CI guardrail against API↔plugin type drift
├── shopify-app/                    # Shopify Remix plugin (settings + traffic dashboard + theme extension)
├── AGENT_ISSUERS.md                # how AI agent issuers register with the directory
└── Dockerfile + docker-compose.yml + .github/workflows/ci.yml

Status

API POST /verify ✅ Fastify + TypeScript + structured pino logs
Multi-protocol verifier ✅ MultiProtocolVerifier sniffs the request and dispatches
Visa Trusted Agent Protocol (real wire format) ✅ agent-browser-auth/agent-payer-auth tags, ed25519 + rsa-pss-sha256, 8-min window cap, nonce replay block, Consumer Recognition Object + Visa-signed PS256 IdToken (verified against mcp.visa.com/.well-known/jwks), Agentic Payment Container validation
AVA TAP-style profile ✅ RFC 9421 + Ed25519, content-digest, mandatory alg/created/nonce, server-side max signature age, x-ava-mandate
Google AP2 (v0.2) ✅ dSD-JWT delegation chains (RFC 9901 + draft-gco-oauth-delegate-sd-jwt): open→closed Checkout Mandate with checkout_jwt hash linkage, Payment Mandate with transaction_id linkage, pluggable constraint-evaluator registries (allowed_merchants, line_items, amount_range, allowed_payees, reference), aud + single-use-nonce binding; v0.1 Intent/Cart traffic gets typed unsupported_protocol_version
IETF Web Bot Auth ✅ draft-ietf-webbotauth-httpsig-protocol-00 (formerly draft-meunier-webbotauth-httpsig-protocol/-directory): tag="web-bot-auth", Signature-Agent (both wire forms), RFC 7638 thumbprint keyids, allowlisted /.well-known/http-message-signatures-directory resolution, identity-only result (no mandate)
Replay protection ✅ nonce + cart-jti dedup via ReplayGuard (in-memory now, Redis-ready interface)
Rate limiting ✅ @fastify/rate-limit, RATE_LIMIT_MAX/min (default 300) per client IP; TRUST_PROXY_HOPS (default 1, Railway's edge) sets how many X-Forwarded-For hops are trusted, and /healthz echoes the derived clientIp so the setting can be checked from outside
Fail-closed directory writes ✅ no DIRECTORY_REGISTRATION_TOKEN → writes disabled (opt-in dev flag AVA_ALLOW_OPEN_DIRECTORY_WRITES=1); constant-time token compare
Federated key resolution ✅ chain: Visa Agentic Directory (when credentialed) → Visa public JWKS → Web Bot Auth agent cards → hosted/private allowlist; kid- + protocol- + algorithm-aware (replaced keys[0]); down roots skipped, revocations never shadowed
Agent Directory + caching ✅ 5-min TTL keyed by (id, hints), caches misses, Remote/Static/StorageBacked impls
Hosted Agent Directory ✅ /.well-known/ava-agent-directory, register/lookup/revoke, file-backed storage
Public agent SDK ✅ @ava-pay/agent@0.2.0 — all four protocol signers + verifier primitives
Public landing page + live demo ✅ public/ — Web Crypto Ed25519 in the browser, signs and verifies against a pre-seeded public demo agent
Agent issuer onboarding ✅ AGENT_ISSUERS.md
Runnable demos ✅ npm run demo, npm run demo:ap2, npm run demo:wba, npm run demo:tap
API tests ✅ 597 with real cryptography (real TAP + AVA profile + AP2 + Web Bot Auth + federated resolution + directory + caching + replay/hardening)
Shopify plugin ✅ OAuth, Polaris settings, traffic dashboard, App Proxy pass-through
Plugin tests ✅ 15
Dockerfile + compose with Redis ✅
CI (typecheck + tests + drift + docker build) ✅
Real Visa Agent Directory wiring ⏳ partner-gated — VISA_AGENT_DIRECTORY_URL swaps in zero-code
Real Google AP2 attestation roots ⏳ awaiting public spec finalization
Redis-backed CachingAgentDirectory ⏳ Redis service provisioned, swap pending
Published @ava-pay/agent npm package ✅ 0.2.0 live (published 2026-07-12)
Self-serve directory auth (DPoP) ⏳ V1 uses bearer token; DPoP next

Run the API

npm install
npm run dev          # http://localhost:3000  (logs JSON to stdout)

Or in Docker:

docker compose up --build    # API on :3000, Redis on :6379 (ready for the cache swap)

Test it

npm test                  # 597 real-cryptography tests across all four protocols + directory
npm run typecheck         # full TS strict check
npm run check:type-sync   # API ↔ plugin VerificationFailureReason drift guard
npm run demo              # in-process AVA TAP profile demo
npm run demo:tap          # in-process real Visa TAP demo
npm run demo:ap2          # in-process AP2 v0.2 demo
npm run demo:wba          # in-process Web Bot Auth demo

Web Bot Auth key discovery is allowlist-gated: the verifier only fetches /.well-known/http-message-signatures-directory from origins in WBA_ALLOWED_SIGNATURE_AGENTS (comma-separated https origins; defaults to https://chatgpt.com, the only major agent operator publishing a live directory as of July 2026).

Hosted Agent Directory

When the API runs, it also exposes:

Endpoint Purpose
GET /.well-known/ava-agent-directory Discovery — { service, version, count, generatedAt }
GET /directory/agents Public list (revoked agents hidden)
GET /directory/agents/:id Lookup
POST /directory/agents Register / update — bearer-token auth (DIRECTORY_REGISTRATION_TOKEN)
POST /directory/agents/:id/revoke Revoke — bearer-token auth

Storage path is AVA_DIRECTORY_DATA (file-backed JSON, persists across restarts). Defaults to in-memory when unset.

Verifier key resolution is federated: keys resolve through a chain of roots of trust — Visa's Agentic Directory (when VISA_AGENT_DIRECTORY_URL is set) → Visa's public JWKS (mcp.visa.com) → the Web Bot Auth key directories of allowlisted signature agents → this hosted directory as the private-allowlist fallback. The first root that knows the key wins (a revocation in a higher root is definitive); a root that is down is skipped. Agents you register here are immediately resolvable by /verify — and an agent that already publishes a Web Bot Auth key card can sign Visa TAP requests with that same key (keyid = RFC 7638 thumbprint), no separate registration.

See AGENT_ISSUERS.md for the agent-side onboarding flow.

Operator provenance (optional)

A verified result answers which agent signed this. It does not answer who is accountable for the origin that agent claims to be. An OperatorSource answers the second question, separately, and it can never change the first: annotateWithOperator only runs for trusted: true, attaches its answer at result.operator, and treats a null, a throw or a missing source as identical no-ops.

WhisperOperatorSource (src/verifier/whisper-operator-source.ts) implements it against the Whisper graph, which is public and needs no key. Each origin you pass to describe() is sent to https://graph.whisper.online/api/query, an endpoint operated by Whisper Security, as a hostname and its registrable parent. No request content, no buyer data and no signing-key material goes with it, and nothing is sent at all until you call describe(). Switch it on by passing it as operator:

import { MultiProtocolVerifier } from './src/verifier/multi.js';
import { WhisperOperatorSource } from './src/verifier/whisper-operator-source.js';

const verifier = new MultiProtocolVerifier({
  /* ...existing options... */
  operator: new WhisperOperatorSource(),          // keyless
  // operator: new WhisperOperatorSource({ apiKey: process.env.WHISPER_API_KEY }),
});

See docs/operator-sources/whisper.md for the record it returns, what each field does and does not claim, and how to run it against the live graph. See docs/RESOLVER-SOURCES.md for the contract an operator source owes its caller.

Architecture

The seam is src/verifier/interface.ts — AgentVerifier, one method. MultiProtocolVerifier (src/verifier/multi.ts) sniffs each incoming request's signature tag and dispatches: web-bot-auth → WebBotAuthVerifier, agent-browser-auth/agent-payer-auth → VisaTapVerifier (real Visa wire format), ap2-checkout-mandate header → Ap2AgentVerifier, untagged RFC 9421 → VisaAgentVerifier (AVA's TAP-style profile). Every verifier returns the same typed VerificationResult; none of them throw. Adding a protocol = one new implementation + a sniff rule. The full module map is in Repo layout above.

Real Visa Protocol — getting your Visa API credentials

The Trusted Agent Protocol (TAP) is published by Visa. The on-the-wire format — RFC 9421 HTTP Message Signatures, Consumer Recognition Objects, Visa-signed IdTokens, Agentic Payment Containers — is what VisaTapVerifier already implements, validated against Visa's live public JWKS. What's partner-gated is access to Visa's hosted Agentic Directory (their authoritative source of agent public keys + revocation status).

To wire that in:

  1. Apply for Visa Partners access at https://developer.visa.com (look for "Trusted Agent Protocol" / "AI Commerce"). You'll receive VISA_API_KEY and the VISA_AGENT_DIRECTORY_URL for your environment (sandbox vs prod).

  2. Set environment variables before starting the API:

    export VISA_AGENT_DIRECTORY_URL=https://agent-directory.visa.com    # provisioned per-tenant
    export VISA_API_KEY=...                                              # your bearer token
    npm run dev

    On boot, buildServer detects VISA_AGENT_DIRECTORY_URL and switches the directory from StaticAgentDirectory (dev) to CachingAgentDirectory(RemoteAgentDirectory(...), 5min) automatically.

  3. No code changes needed. Verification, mandate scoping, content-digest enforcement, and revocation handling all flow through the same AgentVerifier interface.

While you wait for partner access, dev mode pre-loads an empty StaticAgentDirectory. The test suite uses the same primitive, generating fresh Ed25519 keypairs at test boot — that's how every assertion in tests/verify.test.ts exercises real cryptography end-to-end.

Performance

The route emits an x-ava-verify-ms response header so we can monitor the <50 ms target end-to-end. The full Visa pipeline (parse + signature window + content-digest + cached directory hit + Ed25519 verify + mandate decode + scope check) runs in single-digit milliseconds locally; the only sub-50ms risk is a cold directory miss on first agent contact (Visa Agent Directory round-trip).

Shopify plugin

Lives in shopify-app/. Two-minute install:

  1. cd shopify-app && cp .env.example .env — fill in Partners API keys + AVA_PAY_API_URL.
  2. npm install && npm run setup && npm run dev — Shopify CLI tunnels and opens the install on your dev store.
  3. In your dev store theme editor: Online Store → Customize → App embeds → AVA Pay → ON.

Or, instead of step 3, paste this single line into theme.liquid:

<script async src="{{ shop.url }}/apps/ava-pay/embed.js"></script>

Storefront pages now ping the App Proxy → AVA Pay /verify → mint a one-time discount code → redirect verified agents through /discount/AVA-XXXXXXXX. Merchant policy (toggle + max-cap) is configured in the embedded admin UI; full architecture diagram and dev walkthrough live in shopify-app/README.md.

License

MIT. See LICENSE. The published SDK, @ava-pay/agent, carries the same license.

A verifier nobody can read is a verifier nobody should trust, so the decision logic, the signature bases, and the test suite are all public. Read them before you rely on a verdict.

AVA Pay™ and AVA Ready™ are trademarks of Agentic Verification Architecture LLC. The license above covers the code, not the names.

About

Merchant-side trust gateway for AI shopping agents. One API verifies Visa Trusted Agent Protocol, Google AP2 v0.2, and IETF Web Bot Auth (real ChatGPT traffic): RFC 9421, Ed25519, SD-JWT. Live on the Shopify App Store.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages