VetoVetoDocs
Guides

Add agentic checkout to your site

The stack-agnostic way to make your store discoverable and buyable by AI agents — the discovery anchor, the manifest, and mounting the protocol on Node, Next.js, Express, or Hono (or any framework).

To make your store buyable by an AI agent, you expose one discovery URL and four endpoints behind it. An agent starts at the discovery anchor, reads what you sell and where to pay, and follows the next field in every response — it never reads these docs at runtime. This guide is the connect path: where the URLs live, and how to mount them on whatever you already run.

The contract: one anchor, four endpoints

GET  /.well-known/agentic-checkout.json   ← the discovery anchor (start here)
GET  /agent/catalog                       ← your product list
POST /agent/checkout                       ← create a session, get a payment instruction
POST /agent/checkout/{id}/settle           ← submit payment, run the acceptance gate
GET  /agent/checkout/{id}                   ← poll session state

The anchor advertises everything else — the catalog URL, the checkout URL, your supported rails, your receiving address (pay_to), and a summary of your policy so a well-behaved agent can self-screen before it even tries:

GET /.well-known/agentic-checkout.json
{
  "protocol": "veto-checkout/0.1",
  "merchant": { "id": "acme", "name": "Acme Corp", "domain": "shop.acme.example" },
  "catalog_url": "/agent/catalog",
  "checkout_url": "/agent/checkout",
  "rails": ["x402", "mock"],
  "pay_to": { "x402": { "chain": "base", "address": "0x…", "asset": "USDC" } },
  "mandate": { "accepted": ["veto", "ap2", "acp", "none"], "preferred": "veto" },
  "policy_summary": { "require_mandate_over_usd": 20, "max_per_transaction_usd": 500, "min_reputation_tier": "standard" }
}

You don't hand-write this. createCheckout() builds it from your config, and the adapter serves it at the right path. The URLs inside are relative, so the same manifest works whether you mount at a root domain, a subdomain, or a path prefix.

Two ways to connect

See self-host vs hosted for the full tradeoff.

Hosted: point DNS at Veto

In hosted mode you never run the protocol. Create the merchant, then make the discovery anchor resolve under your brand.

Create the merchant

curl -X POST https://api.veto-checkout.com/v1/merchants \
  -H "Authorization: Bearer veto_test_…" -H "Content-Type: application/json" \
  -d '{ "slug": "acme", "name": "Acme", "domain": "shop.acme.example",
        "receiving": { "x402": { "chain": "base-sepolia", "address": "0xYourAddr…", "asset": "USDC" } } }'

Add your catalog and policy (policies & trust), then POST /v1/publish.

Route the discovery anchor

Your merchant is immediately live at the Veto-served forms:

https://acme.veto-checkout.com/.well-known/agentic-checkout.json   (subdomain)
https://api.veto-checkout.com/m/acme/.well-known/agentic-checkout.json   (path)

To serve it under your own brand, add a CNAME so shop.acme.example resolves to your Veto subdomain. Agents that discover you at your domain get a manifest whose relative URLs resolve back to the same host — discovery "just works" on either form.

Verify discovery

curl https://acme.veto-checkout.com/.well-known/agentic-checkout.json

You should see your merchant block, catalog/checkout URLs, pay_to, and policy_summary. The matching receipt JWKS is served at /.well-known/jwks.json.

Self-hosted: mount the SDK on your stack

createCheckout(config) returns a handle whose methods map 1:1 to the five endpoints. An HTTP adapter wires those methods to your framework. The core has zero runtime dependencies; framework adapters resolve your framework lazily, so nothing is imported until you call it.

First, build the handle once (shared by every adapter below):

checkout.ts
import { createCheckout, BALANCED } from '@veto-protocol/checkout';

export const checkout = createCheckout({
  merchant: { id: 'acme', name: 'Acme', domain: 'shop.acme.example' },
  catalog: [
    { sku: 'rpt-001', name: 'Market Report', price: { amount: '5.00', currency: 'USD' }, available: true },
  ],
  receiving: { x402: { chain: 'base', address: process.env.VETO_X402_ADDRESS!, asset: 'USDC' } },
  policy: BALANCED(),
  receiptSigningKeySeedB64: process.env.VETO_RECEIPT_SEED,
});

Then pick your adapter.

The zero-dependency node:http adapter — nothing to install:

server.ts
import { createNodeServer } from '@veto-protocol/checkout/node';
import { checkout } from './checkout.ts';

createNodeServer(checkout).listen(8787, () => {
  console.log('manifest at http://localhost:8787/.well-known/agentic-checkout.json');
});

A single catch-all route handler. Place it at app/agent/[...veto]/route.ts:

app/agent/[...veto]/route.ts
import { vetoCheckoutNext } from '@veto-protocol/checkout/next';
import { checkout } from '@/checkout';

export const { GET, POST } = vetoCheckoutNext(checkout);

Serve the discovery anchor and JWKS at the well-known paths (a tiny route or a rewrite to the handler), so GET /.well-known/agentic-checkout.json resolves on your domain.

vetoCheckoutRouter mounts all five routes (and parses JSON for you):

server.ts
import express from 'express';
import { vetoCheckoutRouter } from '@veto-protocol/checkout/express';
import { checkout } from './checkout.ts';

const app = express();
app.use(vetoCheckoutRouter(checkout)); // mount at the domain root
app.listen(8787);

Express is an optional peer dependency — installed in your app, never bundled by the SDK.

vetoCheckoutHono returns a sub-app you mount at the root:

server.ts
import { Hono } from 'hono';
import { vetoCheckoutHono } from '@veto-protocol/checkout/hono';
import { checkout } from './checkout.ts';

const app = new Hono();
app.route('/', vetoCheckoutHono(checkout)); // all 5 routes + /.well-known/jwks.json
export default app;

Every adapter funnels through one pure function. If your framework isn't listed, call it directly — normalize your request in, serialize the response out:

adapter.ts
import { handleRequest } from '@veto-protocol/checkout/handler';
import { checkout } from './checkout.ts';

// In your framework's handler:
const { status, headers, body } = await handleRequest(checkout, {
  method: req.method,
  path: url.pathname,        // leading-slash path only, no query/host
  body: await req.json().catch(() => undefined),
  headers: Object.fromEntries(req.headers),
});
// → reply with `status`, `headers`, and JSON.stringify(body)

handleRequest routes all five endpoints, emits the { reason_codes, error_human } error shape, and uses HTTP status codes semantically — so routing semantics live in exactly one place no matter which framework wraps it.

Confirm an agent can buy

Whichever path you chose, the proof is the same: discover, quote, settle. With the SDK's CLI:

veto discover http://localhost:8787
veto quote    http://localhost:8787 --sku rpt-001 --qty 1
veto pay      http://localhost:8787 --session <id> --mock

A successful pay returns an accepted status, an order_id, and a signed receipt. That receipt is the proof the sale happened — anyone can verify it offline.

Next steps