VetoVetoDocs
Guides

Add Veto with your AI coding agent

Veto is built on the same standard patterns as Stripe — a REST API, a Bearer key, and HMAC-signed webhooks — so your AI coding tool (Cursor, Claude Code, Copilot) already understands it. You can have your agent set up your store and wire fulfillment into your app in minutes.

In one line: Veto sells for you; you deliver. When an agent buys, Veto sends your app a signed webhook, and your app fulfills the order — ship it, email the file, grant access — exactly like you would on a Stripe sale. There's no proprietary delivery engine and no file or license storage inside Veto: Veto governs the sale; your app fulfills it.

The nice part: Veto uses only standard patterns — a REST API, an Authorization: Bearer veto_… key, and HMAC-signed webhooks. Those are the same patterns behind Stripe, GitHub, and every webhook you've ever wired up. That means your AI coding tool already knows them cold. You can paste a plain-English task into Cursor or Claude Code and it will write the endpoint for you.

Two jobs, two sections. Section 1 sets up your store by conversation (an MCP server your agent drives). Section 2 wires fulfillment into your app (a task you paste into your coding tool). Do them in order.

1. Set up your store by conversation

Before you can take a sale you need a store: a merchant, a catalog, and a receiving address. You don't have to run any of that by hand — the @veto-protocol/mcp-merchant MCP server gives your coding agent tools to create the merchant, add products, set receiving, and publish. You describe your shop in plain English; the agent does the rest.

Drop this .mcp.json in your project (Claude Code and Cursor both read it), with a veto_test_ key so your agent provisions a sandbox store first:

.mcp.json
{
  "mcpServers": {
    "veto-merchant": {
      "command": "npx",
      "args": ["-y", "@veto-protocol/mcp-merchant"],
      "env": {
        "VETO_API_KEY": "veto_test_8f2c…",
        "VETO_API_BASE": "https://merchants.veto-ai.com"
      }
    }
  }
}

Restart your agent so it loads the tools, then give it a prompt like:

Set up my Veto store using the veto-merchant MCP tools.

1. Run veto_status to confirm my key works and whether I'm in test or live mode.
2. Create a merchant: slug "acme", name "Acme Corp", domain "shop.acme.example".
3. Add one product: sku "rpt-001", name "Market Report", price "5.00" USD, available.
4. Set receiving to my Base Sepolia USDC address
   0x1111111111111111111111111111111111111111 (I'm in test mode).
5. Run veto_validate; if there are findings, fix them and tell me what changed.
6. Publish with veto_publish and show me the active_policy_version.

Keep all prices as exact decimal strings. Stop and ask me before anything
that would move real money.

Keep your key out of version control.

VETO_API_KEY is a secret. Use your agent's env-var substitution (e.g. "${VETO_API_KEY}") or your editor's secret store rather than committing the literal key — and never put a veto_live_ key in a shared repo.

The full walkthrough — every tool and the go-live cutover — is in Set up Veto with your coding agent (MCP).

2. Wire fulfillment with your AI

Now the part that makes money real: when a sale settles, Veto POSTs a signed webhook to your app, and your app fulfills the order. You write one endpoint. Your AI coding tool can write it for you — copy this task into Cursor, Claude Code, or Copilot and let it work:

Copy-paste this into your coding tool.

Add Veto to my app.

1. Add a webhook endpoint at POST /veto/webhook.
2. Read the RAW request body (do NOT JSON.parse then re-stringify — the signature
   is over the exact bytes).
3. Verify the "Veto-Signature" header. It looks like  t=<unix>,v1=<hex>  and the
   v1 value is HMAC-SHA256 of the string "<t>.<rawBody>" using my signing secret,
   which is in the env var VETO_WEBHOOK_SECRET. Reject with 400 if it doesn't match
   or the timestamp is more than 300 seconds old.
   If this is a Node/TypeScript app, use the @veto-protocol/checkout verifyWebhook
   helper instead of hand-rolling the HMAC.
4. Parse the JSON. On event.type === "order.settled", fulfill the order:
   the payload is at event.data.object and has items (an array of { sku, qty }),
   total, and buyer (buyer.name and buyer.shipping_address for physical goods).
   Ignore order.accepted, order.rejected, and order.held for now.
5. Dedupe on the "Veto-Event-Id" header so the same order isn't fulfilled twice.
6. Return 200 quickly; move slow work (shipping calls, file generation) to a queue.

That task encodes the real wire contract, so whatever your tool generates will match what Veto sends. If your app is Node/TypeScript, the SDK ships a verifier so you never hand-roll the crypto — here's what the endpoint looks like:

veto-webhook.ts
import { verifyWebhook } from '@veto-protocol/checkout';

export async function POST(req: Request) {
  const raw = await req.text();                        // verify the RAW bytes
  const signature = req.headers.get('veto-signature'); // "t=…,v1=…"

  const result = verifyWebhook(raw, signature, process.env.VETO_WEBHOOK_SECRET!);
  if (!result.ok) {
    return new Response(`invalid signature: ${result.reason}`, { status: 400 });
  }

  const event = JSON.parse(raw);
  if (event.type === 'order.settled') {
    const order = event.data.object;
    // order.items → [{ sku, qty }], order.total, order.buyer → fulfill here.
  }
  return new Response('ok', { status: 200 });
}

What the payload looks like

Every delivery has the same shape. The important fields for fulfillment are items, total, and buyer:

order.settled
{
  "id": "evt_01J…",
  "type": "order.settled",
  "created": 1750000000,
  "livemode": false,
  "api_version": "v1",
  "data": {
    "object": {
      "id": "ord_01J…",
      "session_id": "sess_…",
      "merchant_id": "mrch_01J…",
      "agent_id": "11111111-1111-1111-1111-111111111111",
      "total": { "currency": "USD", "subtotal": "39.00", "tax": "0.00", "total": "39.00" },
      "items": [{ "sku": "slipper-cloud-9", "qty": 1 }],
      "rail_name": "x402",
      "settlement_ref": "0x…txHash…",
      "receipt_id": "rcpt_01J…",
      "buyer": {
        "name": "Ada Lovelace",
        "shipping_address": {
          "line1": "12 Mathematician's Way",
          "city": "London",
          "postal_code": "EC1A 1BB",
          "country": "GB"
        }
      }
    }
  }
}

The four events, and what to do on each:

EventMeansFulfill?
order.acceptedAccepted, receipt issued — before the money is captured.not yet
order.settledThe rail confirmed settlement — the money is yours.yes, deliver here
order.rejectedTerminal reject; nothing settled.no
order.heldRouted to human review.no

Fulfill on order.settled only. A settled order also fired an earlier accepted — wait for settled.

Where to get the two secrets

Both live in your dashboard at merchants.veto-ai.com:

Open Sale notifications (/dashboard/notifications). Add your endpoint URL (e.g. https://api.acme.example/veto/webhook), pick which events you want, and copy the whsec_… signing secret it shows you. Put that value in VETO_WEBHOOK_SECRET — it's the key verifyWebhook uses.

The secret is shown once.

Copy it into your secret manager right away — Veto won't show it again. If you lose it, rotate the endpoint's secret from the same page.

Open Developers and mint an API key. You only need this if your fulfillment calls the REST API back (for example, to read order details). Send it as a Bearer token — Authorization: Bearer veto_test_… — and store it in your secret manager, never in the repo. A veto_test_ key works against your sandbox store; swap to veto_live_ when you go live.

The full wire contract — headers, the ±300s replay window, at-least-once delivery, the retry schedule, and secret rotation — is in the Webhooks reference. The REST API key details are in Authentication.

3. For AI search engines

Veto publishes machine-readable docs so any AI — a coding agent or an answer engine — can read how to integrate without a human in the loop. Point your tool at https://docs.veto-ai.com and it can follow the same steps you just did.